/*-------------------------------------------------------------------------
 *
 * xact.c
 *	  顶级事务系统支持例程
 *
 * 请参阅 src/backend/access/transam/README 以获取更多信息。
 *
 * Portions Copyright (c) 1996-2022, PostgreSQL Global Development Group
 * Portions Copyright (c) 1994, Regents of the University of California
 *
 *
 * IDENTIFICATION
 *	  src/backend/access/transam/xact.c
 *
 *-------------------------------------------------------------------------
 */

#include "postgres.h"

#include <time.h>
#include <unistd.h>

#include "access/commit_ts.h"
#include "access/multixact.h"
#include "access/parallel.h"
#include "access/subtrans.h"
#include "access/transam.h"
#include "access/twophase.h"
#include "access/xact.h"
#include "access/xlog.h"
#include "access/xloginsert.h"
#include "access/xlogrecovery.h"
#include "access/xlogutils.h"
#include "catalog/index.h"
#include "catalog/namespace.h"
#include "catalog/pg_enum.h"
#include "catalog/storage.h"
#include "commands/async.h"
#include "commands/tablecmds.h"
#include "commands/trigger.h"
#include "common/pg_prng.h"
#include "executor/spi.h"
#include "libpq/be-fsstubs.h"
#include "libpq/pqsignal.h"
#include "miscadmin.h"
#include "pg_trace.h"
#include "pgstat.h"
#include "replication/logical.h"
#include "replication/logicallauncher.h"
#include "replication/origin.h"
#include "replication/snapbuild.h"
#include "replication/syncrep.h"
#include "replication/walsender.h"
#include "storage/condition_variable.h"
#include "storage/fd.h"
#include "storage/lmgr.h"
#include "storage/md.h"
#include "storage/predicate.h"
#include "storage/proc.h"
#include "storage/procarray.h"
#include "storage/sinvaladt.h"
#include "storage/smgr.h"
#include "utils/builtins.h"
#include "utils/catcache.h"
#include "utils/combocid.h"
#include "utils/guc.h"
#include "utils/inval.h"
#include "utils/memutils.h"
#include "utils/relmapper.h"
#include "utils/snapmgr.h"
#include "utils/timeout.h"
#include "utils/timestamp.h"

/*
 * 用户可调整参数
 */
int			DefaultXactIsoLevel = XACT_READ_COMMITTED;
int			XactIsoLevel;

bool		DefaultXactReadOnly = false;
bool		XactReadOnly;

bool		DefaultXactDeferrable = false;
bool		XactDeferrable;

int			synchronous_commit = SYNCHRONOUS_COMMIT_ON;

#ifdef FDDQ
bool		enable_logical_replication = true;
#endif

/*
 * CheckXidAlive 是一个 xid 值，指向一个可能正在进行中的（子）事务。
 * 目前，它用于逻辑解码。可能在解码进行时，这种事务会被中止，
 * 在这种情况下，我们会跳过对该特定事务的解码。为了确保
 * 我们在从系统表中获取元组后检查 CheckXidAlive 是否被中止。
 * 我们还确保在逻辑解码期间，我们不会直接访问 tableam 或 heap API，
 * 因为我们仅在 systable_* API 中检查并发中止。
 */
TransactionId CheckXidAlive = InvalidTransactionId;
bool		bsysscan = false;

/*
 * 当作为并行工作线程运行时，我们只在并行工作线程的状态栈上放置
 * 一个 TransactionStateData，那里反映的 XID 将是启动并行处理的
 * 后端中当前活动的 *最内层* 子事务的 XID。但是，
 * GetTopTransactionId() 和 TransactionIdIsCurrentTransactionId()
 * 需要在并行工作线程中返回与用户后端相同的答案，因此
 * 我们需要一些额外的记账。
 *
 * XactTopFullTransactionId 存储我们的顶级事务的 XID，
 * 这将在普通后端中与 TopTransactionStateData.fullTransactionId 相同；
 * 但在并行后端中，后者并没有整个事务状态，它将被从启动
 * 并行操作的后端复制过来。
 *
 * nParallelCurrentXids 在普通后端中将为 0，ParallelCurrentXids 为 NULL，
 * 但在并行后端中，nParallelCurrentXids 将包含需要被视为当前的 XIDs 数量，
 * 而 ParallelCurrentXids 将包含这些 XIDs 本身。这包括了在并行操作开始时
 * 父事务中所有当前或子提交的 XIDs。XIDs 按数字顺序（而不是逻辑顺序）
 * 存储，以使查找尽可能快。
 */
static FullTransactionId XactTopFullTransactionId = {InvalidTransactionId};
static int	nParallelCurrentXids = 0;
static TransactionId *ParallelCurrentXids;

/*
 * 杂项标志位记录在顶层事务中发生的事件。这些标志仅保存在 MyXactFlags 中，
 * 目的是为了在事务后续中让我们记得做某些事情。这个是全局可访问的，
 * 因此可以从代码中任何需要记录标志的地方设置。
 */
int			MyXactFlags;

/*
 * 事务状态 - 从服务器的角度看事务状态
 */
typedef enum TransState
{
	TRANS_DEFAULT,				/* 空闲 */
	TRANS_START,				/* 事务开始 */
	TRANS_INPROGRESS,			/* 在有效事务中 */
	TRANS_COMMIT,				/* 提交进行中 */
	TRANS_ABORT,				/* 中止进行中 */
	TRANS_PREPARE				/* 准备进行中 */
} TransState;

/*
 * 事务块状态 - 客户端查询的事务状态
 *
 * 注意：子事务状态仅用于非顶层事务；
 * 其他状态仅出现在顶层事务中。
 */
typedef enum TBlockState
{
	/* 非事务块状态 */
	TBLOCK_DEFAULT,				/* 空闲 */
	TBLOCK_STARTED,				/* 运行单查询事务 */

	/* 事务块状态 */
	TBLOCK_BEGIN,				/* 启动事务块 */
	TBLOCK_INPROGRESS,			/* 活动事务 */
	TBLOCK_IMPLICIT_INPROGRESS, /* 事务开始后即刻生效的事务 */
	TBLOCK_PARALLEL_INPROGRESS, /* 并行工作者内部的实时事务 */
	TBLOCK_END,					/* 收到 COMMIT */
	TBLOCK_ABORT,				/* 事务失败，等待 ROLLBACK */
	TBLOCK_ABORT_END,			/* 事务失败，收到 ROLLBACK */
	TBLOCK_ABORT_PENDING,		/* 实时事务，收到 ROLLBACK */
	TBLOCK_PREPARE,				/* 实时事务，收到 PREPARE */

	/* 子事务状态 */
	TBLOCK_SUBBEGIN,			/* 开始一个子事务 */
	TBLOCK_SUBINPROGRESS,		/* 实时子事务 */
	TBLOCK_SUBRELEASE,			/* 收到 RELEASE */
	TBLOCK_SUBCOMMIT,			/* 在 TBLOCK_SUBINPROGRESS 时收到 COMMIT */
	TBLOCK_SUBABORT,			/* 子事务失败，等待 ROLLBACK */
	TBLOCK_SUBABORT_END,		/* 子事务失败，收到 ROLLBACK */
	TBLOCK_SUBABORT_PENDING,	/* 实时子事务，收到 ROLLBACK */
	TBLOCK_SUBRESTART,			/* 实时子事务，收到 ROLLBACK TO */
	TBLOCK_SUBABORT_RESTART		/* 子事务失败，收到 ROLLBACK TO */
} TBlockState;

/*
 *	事务状态结构
 */
typedef struct TransactionStateData
{
	FullTransactionId fullTransactionId;	/* 我的 FullTransactionId */
	SubTransactionId subTransactionId;	/* 我的子事务 ID */
	char	   *name;			/* 保存点名称（如有） */
	int			savepointLevel; /* 保存点级别 */
	TransState	state;			/* 低级别状态 */
	TBlockState blockState;		/* 高级别状态 */
	int			nestingLevel;	/* 事务嵌套深度 */
	int			gucNestLevel;	/* GUC 上下文嵌套深度 */
	MemoryContext curTransactionContext;	/* 我的事务生命周期上下文 */
	ResourceOwner curTransactionOwner;	/* 我的查询资源 */
	TransactionId *childXids;	/* 已提交的子 XID，按 XID 顺序 */
	int			nChildXids;		/* 已提交的子 XID 数量 */
	int			maxChildXids;	/* childXids[] 的分配大小 */
	Oid			prevUser;		/* 先前的 CurrentUserId 设置 */
	int			prevSecContext; /* 先前的 SecurityRestrictionContext */
	bool		prevXactReadOnly;	/* 进入时间的事务只读状态 */
	bool		startedInRecovery;	/* 我们是否在恢复中启动？ */
	bool		didLogXid;		/* XID 是否已包含在 WAL 记录中？ */
	int			parallelModeLevel;	/* 进入/退出并行模式计数器 */
	bool		chain;			/* 在此块后启动新块 */
	bool		topXidLogged;	/* 对于子事务：顶层 XID 是否已记录？ */
	struct TransactionStateData *parent;	/* 指向父事务的链接 */
} TransactionStateData;

typedef TransactionStateData *TransactionState;

/*
 * 用于通过共享内存将事务状态传输到并行工作者的序列化表示。
 */
typedef struct SerializedTransactionState
{
	int			xactIsoLevel;
	bool		xactDeferrable;
	FullTransactionId topFullTransactionId;
	FullTransactionId currentFullTransactionId;
	CommandId	currentCommandId;
	int			nParallelCurrentXids;
	TransactionId parallelCurrentXids[FLEXIBLE_ARRAY_MEMBER];
} SerializedTransactionState;

/* SerializedTransactionState 的大小，不包括最终数组。 */
#define SerializedTransactionStateHeaderSize \
	offsetof(SerializedTransactionState, parallelCurrentXids)

/*
 * CurrentTransactionState 始终指向当前事务状态块。
 * 当完全不在事务中或在顶层事务中时，它将指向 TopTransactionStateData。
 */
static TransactionStateData TopTransactionStateData = {
	.state = TRANS_DEFAULT,
	.blockState = TBLOCK_DEFAULT,
	.topXidLogged = false,
};

/*
 * unreportedXids 保存所有尚未在 XLOG_XACT_ASSIGNMENT 记录中报告的子事务 XID。
 */
static int	nUnreportedXids;
static TransactionId unreportedXids[PGPROC_MAX_CACHED_SUBXIDS];

static TransactionState CurrentTransactionState = &TopTransactionStateData;

/*
 * 子事务 ID 和命令 ID 分配计数器是整个事务的全局变量，
 * 因此我们不将它们保存在状态栈中。
 */
static SubTransactionId currentSubTransactionId;
static CommandId currentCommandId;
static bool currentCommandIdUsed;

/*
 * xactStartTimestamp 是 transaction_timestamp() 的值。
 * stmtStartTimestamp 是 statement_timestamp() 的值。
 * xactStopTimestamp 是我们记录 commit 或 abort WAL 记录的时间。
 * 这些在我们进入和退出子事务时不会改变，因此我们不
 * 将它们保存在 TransactionState 栈中。
 */
static TimestampTz xactStartTimestamp;
static TimestampTz stmtStartTimestamp;
static TimestampTz xactStopTimestamp;

/*
 * 用于准备当前事务的 GID。这也是
 * 整个事务的全局变量，因此我们不将它保存在状态栈中。
 */
static char *prepareGID;

/*
 * 一些命令希望强制同步提交。
 */
static bool forceSyncCommit = false;

/* 事务中记录语句的标志。 */
bool		xact_is_sampled = false;

/*
 * 事务中止工作的私有上下文 --- 我们在启动时保留这个空间，以确保
 * AbortTransaction 和 AbortSubTransaction 可以在内存耗尽时工作。
 */
static MemoryContext TransactionAbortContext = NULL;

/*
 * 附加的事务开始和结束回调的列表
 */
typedef struct XactCallbackItem
{
	struct XactCallbackItem *next;
	XactCallback callback;
	void	   *arg;
} XactCallbackItem;

static XactCallbackItem *Xact_callbacks = NULL;

/*
 * 附加的子事务开始和结束回调的列表
 */
typedef struct SubXactCallbackItem
{
	struct SubXactCallbackItem *next;
	SubXactCallback callback;
	void	   *arg;
} SubXactCallbackItem;

static SubXactCallbackItem *SubXact_callbacks = NULL;


/* 本地函数原型 */
static void fc_AssignTransactionId(TransactionState fc_s);
static void fc_AbortTransaction(void);
static void fc_AtAbort_Memory(void);
static void fc_AtCleanup_Memory(void);
static void fc_AtAbort_ResourceOwner(void);
static void fc_AtCCI_LocalCache(void);
static void fc_AtCommit_Memory(void);
static void fc_AtStart_Cache(void);
static void fc_AtStart_Memory(void);
static void fc_AtStart_ResourceOwner(void);
static void fc_CallXactCallbacks(XactEvent fc_event);
static void fc_CallSubXactCallbacks(SubXactEvent fc_event,
								 SubTransactionId fc_mySubid,
								 SubTransactionId fc_parentSubid);
static void fc_CleanupTransaction(void);
static void fc_CheckTransactionBlock(bool fc_isTopLevel, bool fc_throwError,
								  const char *fc_stmtType);
static void fc_CommitTransaction(void);
static TransactionId fc_RecordTransactionAbort(bool fc_isSubXact);
static void fc_StartTransaction(void);

static void fc_StartSubTransaction(void);
static void fc_CommitSubTransaction(void);
static void fc_AbortSubTransaction(void);
static void fc_CleanupSubTransaction(void);
static void fc_PushTransaction(void);
static void fc_PopTransaction(void);

static void fc_AtSubAbort_Memory(void);
static void fc_AtSubCleanup_Memory(void);
static void fc_AtSubAbort_ResourceOwner(void);
static void fc_AtSubCommit_Memory(void);
static void fc_AtSubStart_Memory(void);
static void fc_AtSubStart_ResourceOwner(void);

static void fc_ShowTransactionState(const char *fc_str);
static void fc_ShowTransactionStateRec(const char *fc_str, TransactionState fc_state);
static const char *fc_BlockStateAsString(TBlockState fc_blockState);
static const char *fc_TransStateAsString(TransState fc_state);


/* ----------------------------------------------------------------
 * 事务状态访问器
 * ----------------------------------------------------------------
 */

/*
 *	IsTransactionState
 *
 *	如果我们处于有效事务中，则返回 true；也就是说，
 *	可以安全地进行数据库访问，获取重型锁等。
 */
bool IsTransactionState(void)
{
	TransactionState fc_s = CurrentTransactionState;

	/*
	 * TRANS_DEFAULT 和 TRANS_ABORT 显然是危险状态。然而，我们
	 * 还拒绝启动/关闭状态 TRANS_START, TRANS_COMMIT,
	 * TRANS_PREPARE，因为在这些过渡状态中，进行任何有意义的操作可能为时已晚或过早。
	 * 因此，唯一的“有效”状态是 TRANS_INPROGRESS。
	 */
	return (fc_s->state == TRANS_INPROGRESS);
}


#ifdef FDDQ
bool IsAbortedTransactionState(void)
{
	TransactionState fc_s = CurrentTransactionState;

	if(fc_s->state == TRANS_ABORT || IsAbortedTransactionBlockState())
	{
		return true;
	}
	return false;
}
#endif


/*
 *	IsAbortedTransactionBlockState
 *
 *	如果我们处于一个已中止的事务块中，则返回 true。
 */
bool IsAbortedTransactionBlockState(void)
{
	TransactionState fc_s = CurrentTransactionState;

	if (fc_s->blockState == TBLOCK_ABORT ||
		fc_s->blockState == TBLOCK_SUBABORT)
		return true;

	return false;
}


/*
 *	GetTopTransactionId
 *
 * 这将返回主事务的 XID，如果尚未设置则分配一个。
 * 请确保仅在有效的 xact 内调用此函数。
 */
TransactionId GetTopTransactionId(void)
{
	if (!FullTransactionIdIsValid(XactTopFullTransactionId))
		fc_AssignTransactionId(&TopTransactionStateData);
	return XidFromFullTransactionId(XactTopFullTransactionId);
}

/*
 *	GetTopTransactionIdIfAny
 *
 * 如果有分配的主事务 XID，返回其值。
 * 如果我们当前不在事务中，或在尚未分配 XID 的事务中，将返回 InvalidTransactionId。
 */
TransactionId GetTopTransactionIdIfAny(void)
{
	return XidFromFullTransactionId(XactTopFullTransactionId);
}

/*
 *	GetCurrentTransactionId
 *
 * 这将返回当前事务（主事务或子事务）的 XID，如果尚未设置则分配一个。
 * 请确保仅在有效的 xact 内调用此函数。
 */
TransactionId GetCurrentTransactionId(void)
{
	TransactionState fc_s = CurrentTransactionState;

	if (!FullTransactionIdIsValid(fc_s->fullTransactionId))
		fc_AssignTransactionId(fc_s);
	return XidFromFullTransactionId(fc_s->fullTransactionId);
}

/*
 *	GetCurrentTransactionIdIfAny
 *
 * 如果有分配的当前子事务 XID，返回其值。
 * 如果我们当前不在事务中，或在尚未分配 XID 的事务中，将返回 InvalidTransactionId。
 */
TransactionId GetCurrentTransactionIdIfAny(void)
{
	return XidFromFullTransactionId(CurrentTransactionState->fullTransactionId);
}

/*
 *	GetTopFullTransactionId
 *
 * 这将返回主事务的 FullTransactionId，如果尚未设置则分配一个。
 * 请确保仅在有效的 xact 内调用此函数。
 */
FullTransactionId GetTopFullTransactionId(void)
{
	if (!FullTransactionIdIsValid(XactTopFullTransactionId))
		fc_AssignTransactionId(&TopTransactionStateData);
	return XactTopFullTransactionId;
}

/*
 *	GetTopFullTransactionIdIfAny
 *
 * 如果有分配的主事务 FullTransactionId，返回其值。
 * 如果我们当前不在事务中，或在尚未分配的事务中，将返回 InvalidFullTransactionId。
 */
FullTransactionId GetTopFullTransactionIdIfAny(void)
{
	return XactTopFullTransactionId;
}

/*
 *	GetCurrentFullTransactionId
 *
 * 这将返回当前事务（主事务或子事务）的 FullTransactionId，如果尚未设置则分配一个。
 * 请确保仅在有效的 xact 内调用此函数。
 */
FullTransactionId GetCurrentFullTransactionId(void)
{
	TransactionState fc_s = CurrentTransactionState;

	if (!FullTransactionIdIsValid(fc_s->fullTransactionId))
		fc_AssignTransactionId(fc_s);
	return fc_s->fullTransactionId;
}

/*
 *	GetCurrentFullTransactionIdIfAny
 *
 * 这将返回当前子事务的 FullTransactionId（如果已分配）。如果我们当前不在事务中，或在尚未分配 FullTransactionId 的事务中，则返回 InvalidFullTransactionId。
 */
FullTransactionId GetCurrentFullTransactionIdIfAny(void)
{
	return CurrentTransactionState->fullTransactionId;
}

/*
 *	MarkCurrentTransactionIdLoggedIfAny
 *
 * 请记住，当前的 xid - 如果已分配 - 现在已被 wal 记录。
 */
void MarkCurrentTransactionIdLoggedIfAny(void)
{
	if (FullTransactionIdIsValid(CurrentTransactionState->fullTransactionId))
		CurrentTransactionState->didLogXid = true;
}

/*
 * IsSubxactTopXidLogPending
 *
 * 这用于决定我们是否需要为子事务中的操作 WAL 记录顶级 XID。我们要求这样做是为了逻辑解码，见 LogicalDecodingProcessRecord。
 *
 * 如果 wal_level >= logical 并且我们在当前有效的子事务中，且其分配尚未写入任何 WAL 记录，则返回 true。
 */
bool IsSubxactTopXidLogPending(void)
{
	/* 检查它是否已经被记录 */
	if (CurrentTransactionState->topXidLogged)
		return false;

	/* wal_level 必须是逻辑的 */
	if (!XLogLogicalInfoActive())
		return false;

	/* 我们需要处于事务状态 */
	if (!IsTransactionState())
		return false;

	/* 它必须是一个子事务 */
	if (!IsSubTransaction())
		return false;

	/* 子事务必须有一个已分配的 XID */
	if (!TransactionIdIsValid(GetCurrentTransactionIdIfAny()))
		return false;

	return true;
}

/*
 * MarkSubxactTopXidLogged
 *
 * 请记住，当前子事务的顶级事务 id 现在已经被 WAL 记录。
 */
void MarkSubxactTopXidLogged(void)
{
	Assert(IsSubxactTopXidLogPending());

	CurrentTransactionState->topXidLogged = true;
}

/*
 *	GetStableLatestTransactionId
 *
 * 如果事务有 XID，则获取该事务的 XID，否则读取下一个待分配的 XID。一旦我们有了一个值，便在当前事务的剩余时间内返回相同的值。这是为了提供 age(xid) 函数的参考点，但也可能对其他维护任务有用。
 */
TransactionId GetStableLatestTransactionId(void)
{
	static LocalTransactionId fc_lxid = InvalidLocalTransactionId;
	static TransactionId fc_stablexid = InvalidTransactionId;

	if (fc_lxid != MyProc->lxid)
	{
		fc_lxid = MyProc->lxid;
		fc_stablexid = GetTopTransactionIdIfAny();
		if (!TransactionIdIsValid(fc_stablexid))
			fc_stablexid = ReadNextTransactionId();
	}

	Assert(TransactionIdIsValid(fc_stablexid));

	return fc_stablexid;
}

/*
 * AssignTransactionId
 *
 * 为给定的 TransactionState 分配一个新的永久 FullTransactionId。在调用此函数之前，我们不会为事务分配 XID。同时，任何尚未拥有 XID 的父 TransactionStates 也将被分配一个；这保持了不变的条件，即子事务有一个在其父事务之后的 XID。
 */
static void fc_AssignTransactionId(TransactionState fc_s)
{
	bool		fc_isSubXact = (fc_s->parent != NULL);
	ResourceOwner fc_currentOwner;
	bool		fc_log_unknown_top = false;

	/* 确保调用者没有搞错 */
	Assert(!FullTransactionIdIsValid(fc_s->fullTransactionId));
	Assert(fc_s->state == TRANS_INPROGRESS);

	/*
	 * 工作线程在每个并行操作开始时同步事务状态，因此我们不能在这一点上计算新的 XID。
	 */
	if (IsInParallelMode() || IsParallelWorker())
		elog(ERROR, "cannot assign XIDs during a parallel operation");

	/*
	 * 确保父事务有 XID，以便子事务总是拥有比父事务晚的 XID。这里不能递归，否则如果我们处于一个巨大的子事务堆栈底部而这些子事务尚未拥有 XID，可能会导致堆栈溢出。
	 */
	if (fc_isSubXact && !FullTransactionIdIsValid(fc_s->parent->fullTransactionId))
	{
		TransactionState fc_p = fc_s->parent;
		TransactionState *fc_parents;
		size_t		fc_parentOffset = 0;

		fc_parents = palloc(sizeof(TransactionState) * fc_s->nestingLevel);
		while (fc_p != NULL && !FullTransactionIdIsValid(fc_p->fullTransactionId))
		{
			fc_parents[fc_parentOffset++] = fc_p;
			fc_p = fc_p->parent;
		}

		/*
		 * 从技术上讲，这是一个递归调用，但递归永远不会超过一层深。
		 */
		while (fc_parentOffset != 0)
			fc_AssignTransactionId(fc_parents[--fc_parentOffset]);

		pfree(fc_parents);
	}

	/*
	 * 当 wal_level=logical 时，确保一个子事务的 xid 仅在其顶层 xid 已被记录之前才能在 WAL 流中被看到。
	 * 如果必要，我们会记录一个 xact_assignment 记录，包含少于 PGPROC_MAX_CACHED_SUBXIDS 的值。
	 * 请注意，即使一个事务出现在 WAL 记录中，如果 didLogXid 没有被设置，这也是可以的，我们可能只是冗余地记录了一些信息。
	 * 这可能发生在一个 xid 被包含在 WAL 记录中的某个地方，但不在 XLogRecord->xl_xid 中，比如在 xl_standby_locks 中。
	 */
	if (fc_isSubXact && XLogLogicalInfoActive() &&
		!TopTransactionStateData.didLogXid)
		fc_log_unknown_top = true;

	/*
	 * 生成一个新的 FullTransactionId 并在 PG_PROC 和 pg_subtrans 中记录其 xid。
	 *
	 * 注意：我们必须在 Xid 出现在共享存储的其他地方（除了 PG_PROC）之前制作 subtrans 条目；
	 * 因为如果 PG_PROC 中没有足够的空间，subtrans 条目就需要确保其他后端看到 Xid 是 “正在进行” 的。
	 * 参见 GetNewTransactionId。
	 */
	fc_s->fullTransactionId = GetNewTransactionId(fc_isSubXact);
	if (!fc_isSubXact)
		XactTopFullTransactionId = fc_s->fullTransactionId;

	if (fc_isSubXact)
		SubTransSetParent(XidFromFullTransactionId(fc_s->fullTransactionId),
						  XidFromFullTransactionId(fc_s->parent->fullTransactionId));

	/*
	 * 如果这是一个顶层事务，谓词锁定系统也需要被告知。
	 */
	if (!fc_isSubXact)
		RegisterPredicateLockingXid(XidFromFullTransactionId(fc_s->fullTransactionId));

	/*
	 * 获取事务 XID 的锁。（我们假设这不会被阻塞。）我们必须确保锁被分配给事务自己的 ResourceOwner。
	 */
	fc_currentOwner = CurrentResourceOwner;
	CurrentResourceOwner = fc_s->curTransactionOwner;

	XactLockTableInsert(XidFromFullTransactionId(fc_s->fullTransactionId));

	CurrentResourceOwner = fc_currentOwner;

	/*
	 * 每当我们为每个顶层事务发出 WAL 记录时，都会在每个 PGPROC_MAX_CACHED_SUBXIDS 分配的事务 ID 中记录该事务的分配信息。我们
	 * 包含顶层 xid 和所有尚未使用 XLOG_XACT_ASSIGNMENT 记录报告的 subxids。
	 *
	 * 这是为了限制在热备用服务器中所需的共享内存量，以跟踪进行中的 XIDs。详见 RecordKnownAssignedTransactionIds() 的说明。
	 *
	 * 我们不跟踪每个 subxid 的直接父级，仅跟踪每个 subxact 所属的顶层事务。这在恢复中是正确的，因为中止的子事务是单独 WAL 记录的。
	 *
	 * 即使在我们向上递归到他们之前，有几个层级没有分配 xid，这也是正确的。
	 */
	if (fc_isSubXact && XLogStandbyInfoActive())
	{
		unreportedXids[nUnreportedXids] = XidFromFullTransactionId(fc_s->fullTransactionId);
		nUnreportedXids++;

		/*
		 * 确保此测试与 RecoverPreparedTransactions() 中的相似测试匹配
		 */
		if (nUnreportedXids >= PGPROC_MAX_CACHED_SUBXIDS ||
			fc_log_unknown_top)
		{
			xl_xact_assignment fc_xlrec;

			/*
			 * xtop 在此时始终被设置，因为我们递归到事务堆栈的最高未分配 xid 然后再回到下方
			 */
			fc_xlrec.xtop = GetTopTransactionId();
			Assert(TransactionIdIsValid(fc_xlrec.xtop));
			fc_xlrec.nsubxacts = nUnreportedXids;

			XLogBeginInsert();
			XLogRegisterData((char *) &fc_xlrec, MinSizeOfXactAssignment);
			XLogRegisterData((char *) unreportedXids,
							 nUnreportedXids * sizeof(TransactionId));

			(void) XLogInsert(RM_XACT_ID, XLOG_XACT_ASSIGNMENT);

			nUnreportedXids = 0;
			/* 标记顶层，而不是当前 xact 已被记录 */
			TopTransactionStateData.didLogXid = true;
		}
	}
}

/*
 *	GetCurrentSubTransactionId
 */
SubTransactionId
GetCurrentSubTransactionId(void)
{
	TransactionState fc_s = CurrentTransactionState;

	return fc_s->subTransactionId;
}

/*
 *	SubTransactionIsActive
 *
 * 测试指定的 subxact ID 是否仍然有效。请注意，调用者负责检查此 ID 是否与当前 xact 相关。
 */
bool SubTransactionIsActive(SubTransactionId fc_subxid)
{
	TransactionState fc_s;

	for (fc_s = CurrentTransactionState; fc_s != NULL; fc_s = fc_s->parent)
	{
		if (fc_s->state == TRANS_ABORT)
			continue;
		if (fc_s->subTransactionId == fc_subxid)
			return true;
	}
	return false;
}


/*
 *	GetCurrentCommandId
 *
 * 如果调用者打算使用命令 ID 来标记插入/更新/删除的元组，则 “used” 必须为 true。false 意味着该 ID 是为只读目的（例如，作为快照有效性截止点）而获取的。
 * 详见 CommandCounterIncrement() 的讨论。
 */
CommandId GetCurrentCommandId(bool fc_used)
{
	/* 这是全局事务，而非子事务局部 */
	if (fc_used)
	{
		/*
		 * 禁止在并行工作者中设置 currentCommandIdUsed，因为
		 * 我们没有将其传回领导者的机制。 当 currentCommandIdUsed
		 * 在并行操作开始时已为真时，我们可以放宽这一限制。
		 */
		Assert(!IsParallelWorker());
		currentCommandIdUsed = true;
	}
	return currentCommandId;
}

/*
 *	SetParallelStartTimestamps
 *
 * 在并行工作者中，我们应该继承父事务的时间戳，而不是设置自己的时间戳。
 * 并行工作者基础设施必须在调用 StartTransaction() 或
 * SetCurrentStatementStartTimestamp() 之前调用此方法以提供这些值。
 */
void SetParallelStartTimestamps(TimestampTz fc_xact_ts, TimestampTz fc_stmt_ts)
{
	Assert(IsParallelWorker());
	xactStartTimestamp = fc_xact_ts;
	stmtStartTimestamp = fc_stmt_ts;
}

/*
 *	GetCurrentTransactionStartTimestamp
 */
TimestampTz GetCurrentTransactionStartTimestamp(void)
{
	return xactStartTimestamp;
}

/*
 *	GetCurrentStatementStartTimestamp
 */
TimestampTz GetCurrentStatementStartTimestamp(void)
{
	return stmtStartTimestamp;
}

/*
 *	GetCurrentTransactionStopTimestamp
 *
 * 如果事务停止时间尚未设置（可能发生在我们决定不需要记录
 * XLOG 记录时），我们将返回当前时间。
 */
TimestampTz GetCurrentTransactionStopTimestamp(void)
{
	if (xactStopTimestamp != 0)
		return xactStopTimestamp;
	return GetCurrentTimestamp();
}

/*
 *	SetCurrentStatementStartTimestamp
 *
 * 在并行工作者中，这应该已经通过调用 SetParallelStartTimestamps()
 * 提供。
 */
void SetCurrentStatementStartTimestamp(void)
{
	if (!IsParallelWorker())
		stmtStartTimestamp = GetCurrentTimestamp();
	else
		Assert(stmtStartTimestamp != 0);
}

/*
 *	SetCurrentTransactionStopTimestamp
 */
static inline void fc_SetCurrentTransactionStopTimestamp(void)
{
	xactStopTimestamp = GetCurrentTimestamp();
}

/*
 *	GetCurrentTransactionNestLevel
 *
 * 注意：当不在任何事务中时，这将返回零，当在顶级事务中时返回一，等等。
 */
int GetCurrentTransactionNestLevel(void)
{
	TransactionState fc_s = CurrentTransactionState;

	return fc_s->nestingLevel;
}


/*
 *	TransactionIdIsCurrentTransactionId
 */
bool TransactionIdIsCurrentTransactionId(TransactionId fc_xid)
{
	TransactionState fc_s;

	/*
	 * 我们始终说 BootstrapTransactionId 是“不是我的事务 ID”，
	 * 即使它是（例如，在引导期间）。 加上 transam.c 恒久地将
	 * BootstrapTransactionId 视为已提交，这导致 heapam_visibility.c
	 * 例程将所有元组视为已提交，这在引导期间是我们需要的。
	 * （引导模式只插入元组，永远不会更新或删除它们，所以所有元组
	 * 可以立即假定是好的。）
	 *
	 * 同样，InvalidTransactionId 和 FrozenTransactionId 当然
	 * 不是我的事务 ID，因此我们可以立即返回“false”用于
	 * 任何非正常 XID。
	 */
	if (!TransactionIdIsNormal(fc_xid))
		return false;

	if (TransactionIdEquals(fc_xid, GetTopTransactionIdIfAny()))
		return true;

	/*
	 * 在并行工作者中，我们必须视为当前的 XID 存储在
	 * ParallelCurrentXids 而不是事务状态栈中。 注意，
	 * 该数组中的 XID 是按数值排序，而不是按 transactionIdPrecedes 顺序。
	 */
	if (nParallelCurrentXids > 0)
	{
		int			fc_low,
					fc_high;

		fc_low = 0;
		fc_high = nParallelCurrentXids - 1;
		while (fc_low <= fc_high)
		{
			int			fc_middle;
			TransactionId fc_probe;

			fc_middle = fc_low + (fc_high - fc_low) / 2;
			fc_probe = ParallelCurrentXids[fc_middle];
			if (fc_probe == fc_xid)
				return true;
			else if (fc_probe < fc_xid)
				fc_low = fc_middle + 1;
			else
				fc_high = fc_middle - 1;
		}
		return false;
	}

	/*
	 * 我们将对当前子事务的 Xid、其任何已提交子事务、任何
	 * 父事务或其任何之前已提交子事务返回 true。 然而，处于
	 * 中止状态的事务不再是“当前”，即使它可能在状态栈上仍有条目。
	 */
	for (fc_s = CurrentTransactionState; fc_s != NULL; fc_s = fc_s->parent)
	{
		int			fc_low,
					fc_high;

		if (fc_s->state == TRANS_ABORT)
			continue;
		if (!FullTransactionIdIsValid(fc_s->fullTransactionId))
			continue;			/* 它也不能有任何子 XID */
		if (TransactionIdEquals(fc_xid, XidFromFullTransactionId(fc_s->fullTransactionId)))
			return true;
		/* 因为 childXids 数组是有序的，我们可以使用二分搜索 */
		fc_low = 0;
		fc_high = fc_s->nChildXids - 1;
		while (fc_low <= fc_high)
		{
			int			fc_middle;
			TransactionId fc_probe;

			fc_middle = fc_low + (fc_high - fc_low) / 2;
			fc_probe = fc_s->childXids[fc_middle];
			if (TransactionIdEquals(fc_probe, fc_xid))
				return true;
			else if (TransactionIdPrecedes(fc_probe, fc_xid))
				fc_low = fc_middle + 1;
			else
				fc_high = fc_middle - 1;
		}
	}

	return false;
}

/*
 *	TransactionStartedDuringRecovery
 *
 * 如果当前事务是在恢复仍在进行期间启动的，则返回 true。
 * 恢复可能已经结束，因此 RecoveryInProgress() 可能已
 * 返回 false。
 */
bool TransactionStartedDuringRecovery(void)
{
	return CurrentTransactionState->startedInRecovery;
}

/*
 *	EnterParallelMode
 */
void EnterParallelMode(void)
{
	TransactionState fc_s = CurrentTransactionState;

	Assert(fc_s->parallelModeLevel >= 0);

	++fc_s->parallelModeLevel;
}

/*
 *	ExitParallelMode
 */
void ExitParallelMode(void)
{
	TransactionState fc_s = CurrentTransactionState;

	Assert(fc_s->parallelModeLevel > 0);
	Assert(fc_s->parallelModeLevel > 1 || !ParallelContextActive());

	--fc_s->parallelModeLevel;
}

/*
 *	IsInParallelMode
 *
 * 我们是否在并行操作中，作为领导者或工作者之一？检查
 * 这一点以禁止更改后端本地状态的操作，期望在所有工作者之间保持一致。仅仅是缓存通常不需要这样的
 * 限制。以严格的推/弹方式修改的状态，例如活动快照栈，通常是可以的。
 */
bool IsInParallelMode(void)
{
	return CurrentTransactionState->parallelModeLevel != 0;
}

/*
 *	CommandCounterIncrement
 */
void CommandCounterIncrement(void)
{
	/*
	 * 如果命令计数器的当前值没有被“使用”来标记
	 * 元组，我们不需要增加它，因为没有必要区分
	 * 只读命令与其他命令。这有助于推迟命令计数器
	 * 溢出，并保持无操作的 CommandCounterIncrement 操作廉价。
	 */
	if (currentCommandIdUsed)
	{
		/*
		 * 工作者在每个
		 * 并行操作开始时同步事务状态，因此在该
		 * 时点之后我们无法计算新的命令。
		 */
		if (IsInParallelMode() || IsParallelWorker())
			elog(ERROR, "cannot start commands during a parallel operation");

		currentCommandId += 1;
		if (currentCommandId == InvalidCommandId)
		{
			currentCommandId -= 1;
			ereport(ERROR,
					(errcode(ERRCODE_PROGRAM_LIMIT_EXCEEDED),
					 errmsg("cannot have more than 2^32-2 commands in a transaction")));
		}
		currentCommandIdUsed = false;

		/* 将新命令 ID 传播到静态快照中 */
		SnapshotSetCommandId(currentCommandId);

		/*
		 * 使刚刚完成的命令所做的任何目录更改在
		 * 本地 syscache 中可见。显然，在执行
		 * 只读命令后我们不需要这样做。（但请注意 inval.c 中的黑客，以确保我们
		 * 不认为排队 inval 消息的命令是只读的。）
		 */
		fc_AtCCI_LocalCache();
	}
}

/*
 * ForceSyncCommit
 *
 * 接口例程，允许命令强制同步提交当前顶层事务。
 * 目前，两阶段提交不持久化和恢复此变量。
 * 只要所有调用者使用 PreventInTransactionBlock()，这种省略就没有影响。
 */
void ForceSyncCommit(void)
{
	forceSyncCommit = true;
}


/* ----------------------------------------------------------------
 *						StartTransaction stuff
 * ----------------------------------------------------------------
 */

/*
 *	AtStart_Cache
 */
static void fc_AtStart_Cache(void)
{
	AcceptInvalidationMessages();
}

/*
 *	AtStart_Memory
 */
static void fc_AtStart_Memory(void)
{
	TransactionState fc_s = CurrentTransactionState;

	/*
	 * 如果这是第一次，通过时，为
	 * AbortTransaction 创建一个私有上下文。通过现在预留一些空间，我们可以
	 * 隔离 AbortTransaction 免受内存不足场景的影响。像
	 * ErrorContext 一样，我们设置它以缓慢增长和非零最小
	 * 尺寸，以便空间将立即被保留。
	 */
	if (TransactionAbortContext == NULL)
		TransactionAbortContext =
			AllocSetContextCreate(TopMemoryContext,
								  "TransactionAbortContext",
								  32 * 1024,
								  32 * 1024,
								  32 * 1024);

	/*
	 * 我们不应该已经有一个事务上下文。
	 */
	Assert(TopTransactionContext == NULL);

	/*
	 * 为事务创建一个顶层上下文。
	 */
	TopTransactionContext =
		AllocSetContextCreate(TopMemoryContext,
							  "TopTransactionContext",
							  ALLOCSET_DEFAULT_SIZES);

	/*
	 * 在顶层事务中，CurTransactionContext 与
	 * TopTransactionContext 是相同的。
	 */
	CurTransactionContext = TopTransactionContext;
	fc_s->curTransactionContext = CurTransactionContext;

	/* 使 CurTransactionContext 处于活动状态。 */
	MemoryContextSwitchTo(CurTransactionContext);
}

/*
 *	AtStart_ResourceOwner
 */
static void fc_AtStart_ResourceOwner(void)
{
	TransactionState fc_s = CurrentTransactionState;

	/*
	 * 我们不应该已经有一个事务资源拥有者。
	 */
	Assert(TopTransactionResourceOwner == NULL);

	/*
	 * 为事务创建一个顶层资源拥有者。
	 */
	fc_s->curTransactionOwner = ResourceOwnerCreate(NULL, "TopTransaction");

	TopTransactionResourceOwner = fc_s->curTransactionOwner;
	CurTransactionResourceOwner = fc_s->curTransactionOwner;
	CurrentResourceOwner = fc_s->curTransactionOwner;
}

/* ----------------------------------------------------------------
 *						StartSubTransaction stuff
 * ----------------------------------------------------------------
 */

/*
 * AtSubStart_Memory
 */
static void fc_AtSubStart_Memory(void)
{
	TransactionState fc_s = CurrentTransactionState;

	Assert(CurTransactionContext != NULL);

	/*
	 * 创建一个 CurTransactionContext，用于保存在子事务提交后仍然存在但在子事务中止时消失的数据。
	 * 我们将它设置为直接父级的 CurTransactionContext 的子级。
	 */
	CurTransactionContext = AllocSetContextCreate(CurTransactionContext,
												  "CurTransactionContext",
												  ALLOCSET_DEFAULT_SIZES);
	fc_s->curTransactionContext = CurTransactionContext;

	/* 使 CurTransactionContext 处于活动状态。 */
	MemoryContextSwitchTo(CurTransactionContext);
}

/*
 * AtSubStart_ResourceOwner
 */
static void fc_AtSubStart_ResourceOwner(void)
{
	TransactionState fc_s = CurrentTransactionState;

	Assert(fc_s->parent != NULL);

	/*
	 * 为子事务创建一个资源所有者。我们将其设置为直接父级的资源所有者的子级。
	 */
	fc_s->curTransactionOwner =
		ResourceOwnerCreate(fc_s->parent->curTransactionOwner,
							"SubTransaction");

	CurTransactionResourceOwner = fc_s->curTransactionOwner;
	CurrentResourceOwner = fc_s->curTransactionOwner;
}

/* ----------------------------------------------------------------
 *						提交事务内容
 * ----------------------------------------------------------------
 */

/*
 *	记录事务提交
 *
 * 返回 xact 及其子事务中的最新 XID，或者如果 xact 没有 XID 则返回 InvalidTransactionId。
 * （我们在这里计算是因为这样更简单。）
 *
 * 如果更改此函数，请同时查看 RecordTransactionCommitPrepared。
 */
static TransactionId fc_RecordTransactionCommit(void)
{
	TransactionId fc_xid = GetTopTransactionIdIfAny();
	bool		fc_markXidCommitted = TransactionIdIsValid(fc_xid);
	TransactionId fc_latestXid = InvalidTransactionId;
	int			fc_nrels;
	RelFileNode *fc_rels;
	int			fc_nchildren;
	TransactionId *fc_children;
	int			fc_ndroppedstats = 0;
	xl_xact_stats_item *fc_droppedstats = NULL;
	int			fc_nmsgs = 0;
	SharedInvalidationMessage *fc_invalMessages = NULL;
	bool		fc_RelcacheInitFileInval = false;
	bool		fc_wrote_xlog;

	/*
	 * 记录正在进行的事务的逻辑解码的待定失效。
	 * 通常对于 DDL，我们在每个命令结束时记录此内容，
	 * 然而，对于某些情况下我们直接在没有事务块的情况下更新系统表，
	 * 失效将在此时之前不会被记录。
	 */
	if (XLogLogicalInfoActive())
		LogLogicalInvalidations();

	/* 获取提交记录所需的数据 */
	fc_nrels = smgrGetPendingDeletes(true, &fc_rels);
	fc_nchildren = xactGetCommittedChildren(&fc_children);
	fc_ndroppedstats = pgstat_get_transactional_drops(true, &fc_droppedstats);
	if (XLogStandbyInfoActive())
		fc_nmsgs = xactGetCommittedInvalidationMessages(&fc_invalMessages,
													 &fc_RelcacheInitFileInval);
	fc_wrote_xlog = (XactLastRecEnd != 0);

	/*
	 * 如果我们还没有被分配 XID，我们既不能，也不想写入 COMMIT 记录。
	 */
	if (!fc_markXidCommitted)
	{
		/*
		 * 我们期望每个 RelationDropStorage 后面都有一个目录更新，
		 * 因此 XID 赋值，所以我们不应该在这里有任何待定的删除。
		 * 丢弃统计信息也是如此。
		 *
		 * 使用真正的测试而不仅仅是 Assert 来检查这一点，因为这有点脆弱。
		 */
		if (fc_nrels != 0 || fc_ndroppedstats != 0)
			elog(ERROR, "cannot commit a transaction that deleted files but has no xid");

		/* 也不能有子 XIDs；AssignTransactionId 强制执行这一点 */
		Assert(fc_nchildren == 0);

		/*
		 * 没有分配 XID 的事务可以包含失效消息
		 * （例如显式的 relcache 失效或由于原地更新的 catcache 失效）；
		 * 备用需要处理这些。我们不能在没有 XID 的情况下发出提交记录，
		 * 而且我们不想强制分配 XID，因为这对例如
		 * 垃圾回收会有问题。因此我们发出一个定制的记录以处理失效。
		 * 我们不想在发出提交记录的情况下使用它，所以它们
		 * 与提交同步发生（此外，我们不想发出更多的 WAL 记录）。
		 */
		if (fc_nmsgs != 0)
		{
			LogStandbyInvalidations(fc_nmsgs, fc_invalMessages,
									fc_RelcacheInitFileInval);
			fc_wrote_xlog = true;	/* 并非绝对必要 */
		}

		/*
		 * 如果我们没有创建 XLOG 条目，我们就完成了；否则，我们
		 * 应该触发刷新那些条目，与提交记录一样。
		 * 这主要发生在 HOT 修剪等情况下；我们希望这些及时刷新到磁盘。
		 */
		if (!fc_wrote_xlog)
			goto cleanup;
	}
	else
	{
		bool		fc_replorigin;

		/*
		 * 我们是否在使用复制原始功能？或者换句话说，
		 * 我们是否在重放远程操作？
		 */
		fc_replorigin = (replorigin_session_origin != InvalidRepOriginId &&
					  replorigin_session_origin != DoNotReplicateId);

		/*
		 * 开始提交的关键部分并插入提交 XLOG 记录。
		 */
		/* 告诉 bufmgr 和 smgr 准备提交 */
		BufmgrCommit();

		/*
		 * 将我们标记为在“提交关键区间”内。这样
		 * 使得任何并发检查点必须等待，直到我们更新了
		 * pg_xact。没有这个标记，检查点有可能在 XLOG 记录后设置
		 * REDO，但未能将 pg_xact 更新刷新到
		 * 磁盘，从而导致交易提交丢失，如果系统
		 * 稍后崩溃的话。
		 *
		 * 注意：我们可以在 RecordTransactionAbort 中设置此标志，
		 * 但不这么做。因为交易中止的丢失
		 * 是非关键性的；假设它无论如何已被中止。
		 *
		 * 在不持有 ProcArrayLock 的情况下更改我们自己的后端
		 * delayChkptFlags 标志是安全的，因为我们是唯一
		 * 修改它的。虽然这使得检查点对于哪些 xacts
		 * 导致检查点延迟的判断有点模糊，但这并不重要。
		 */
		Assert((MyProc->delayChkptFlags & DELAY_CHKPT_START) == 0);
		START_CRIT_SECTION();
		MyProc->delayChkptFlags |= DELAY_CHKPT_START;

		fc_SetCurrentTransactionStopTimestamp();

		XactLogCommitRecord(xactStopTimestamp,
							fc_nchildren, fc_children, fc_nrels, fc_rels,
							fc_ndroppedstats, fc_droppedstats,
							fc_nmsgs, fc_invalMessages,
							fc_RelcacheInitFileInval,
							MyXactFlags,
							InvalidTransactionId, NULL /* 普通提交 */ );

		if (fc_replorigin)
			/* 向前移动这个复制源的 LSN */
			replorigin_session_advance(replorigin_session_origin_lsn,
									   XactLastRecEnd);

		/*
		 * 记录提交时间戳。如果没有复制源，值来自普通提交
		 * 时间戳；否则，时间戳已在 replorigin_session_origin_timestamp 中通过
		 * 复制设置。
		 *
		 * 我们在这里不需要 WAL 记录任何内容，因为上面写入的提交记录
		 * 已经包含了数据。
		 */

		if (!fc_replorigin || replorigin_session_origin_timestamp == 0)
			replorigin_session_origin_timestamp = xactStopTimestamp;

		TransactionTreeSetCommitTsData(fc_xid, fc_nchildren, fc_children,
									   replorigin_session_origin_timestamp,
									   replorigin_session_origin);
	}

	/*
	 * 检查我们是否想要异步提交。如果 synchronous_commit=off，
	 * 或当前事务没有执行任何 WAL 记录操作或没有分配
	 * 一个 xid，则我们可以允许 XLOG 刷新
	 * 发生异步。即使它有一个 xid，如果它仅对临时和/或未记录的表写入，
	 * 事务可能最终不写入任何 WAL。如果它进行了 HOT 剪枝，它也可能最终
	 * 写入了 WAL 而没有 xid。在崩溃的情况下，这样的事务的丢失将是无关紧要的；
	 * 临时表无论如何会丢失，未记录的表会被截断，HOT 剪枝会在稍后
	 * 再次进行。（鉴于上述情况，你可能会认为在这种情况下完全
	 * 不需要发出 XLOG 记录，但我们目前并未尝试这样做。它肯定会
	 * 在热备份模式中造成问题，因为
	 * KnownAssignedXids 机制需要跟踪每个 XID 分配。它
	 * 可能仅在 wal_level < replica 时跳过，但目前我们
	 * 不这样做。）
	 *
	 * 不过，如果我们正在清理任何非临时关系或提交任何
	 * 想要强制同步提交的命令，则必须立即刷新 XLOG。
	 * （如果有任何要删除的非临时表，我们必须不允许异步提交，
	 * 因为在 COMMIT 记录刷新到磁盘之前，我们可能会删除文件。
	 * 不过，我们确实允许异步提交，如果所有要删除的表都是临时的，因为
	 * 如果我们崩溃，它们无论如何也是会丢失的。）
	 */
	if ((fc_wrote_xlog && fc_markXidCommitted &&
		 synchronous_commit > SYNCHRONOUS_COMMIT_OFF) ||
		forceSyncCommit || fc_nrels > 0)
	{
		XLogFlush(XactLastRecEnd);

		/*
		 * 现在，如果我们在上面写入了一个 COMMIT 记录，
		 * 我们可以更新 CLOG
		 */
		if (fc_markXidCommitted)
			TransactionIdCommitTree(fc_xid, fc_nchildren, fc_children);
	}
	else
	{
		/*
		 * 异步提交情况：
		 *
		 * 这允许在 postmaster 崩溃时可能丢失已提交的事务，因为 WAL 缓冲区未写入。理想情况下，我们可以在没有 fsync 的情况下发出 WAL 写入，但某些 wal_sync_methods 不允许单独的写入/fsync。
		 *
		 * 报告最新的异步提交 LSN，以便 WAL 写入器知道要刷新此提交。
		 */
		XLogSetAsyncXactLSN(XactLastRecEnd);

		/*
		 * 我们不能立即更新 CLOG，因为我们没有刷新 XLOG。相反，我们存储需要在更新 CLOG 之前刷新 XLOG 的 LSN。
		 */
		if (fc_markXidCommitted)
			TransactionIdAsyncCommitTree(fc_xid, fc_nchildren, fc_children, XactLastRecEnd);
	}

	/*
	 * 如果我们进入了提交关键区，现在离开它，让检查点继续进行。
	 */
	if (fc_markXidCommitted)
	{
		MyProc->delayChkptFlags &= ~DELAY_CHKPT_START;
		END_CRIT_SECTION();
	}

	/* 在我们有子 XIDs 方便时计算 latestXid */
	fc_latestXid = TransactionIdLatest(fc_xid, fc_nchildren, fc_children);

	/*
	 * 如果需要，等待同步复制。与上述关于异步提交的决定类似，我们只希望在该后端分配了一个 xid 并写入 WAL 时才等待。如果由于临时/非日志表或由于 HOT 修剪而分配了 xid，则无需等待。
	 *
	 * 请注意，在这个阶段，我们已经标记了 clog，但仍然显示为在 procarray 中运行，并继续保持锁定。
	 */
	if (fc_wrote_xlog && fc_markXidCommitted)
		SyncRepWaitForLSN(XactLastRecEnd, true);

	/* 记住上一个提交记录的结束 */
	XactLastCommitEnd = XactLastRecEnd;

	/* 在下一个事务写入内容之前重置 XactLastRecEnd */
	XactLastRecEnd = 0;
cleanup:
	/* 清理本地数据 */
	if (fc_rels)
		pfree(fc_rels);
	if (fc_ndroppedstats)
		pfree(fc_droppedstats);

	return fc_latestXid;
}


/*
 *	AtCCI_LocalCache
 */
static void fc_AtCCI_LocalCache(void)
{
	/*
	 * 使任何待处理的关系映射更改可见。我们必须在处理本地 sinval 消息之前执行此操作，以便在处理 relcache 无效时将映射更改反映到 relcache 中。
	 */
	AtCCI_RelationMap();

	/*
	 * 使目录更改在下一条命令中对我可见。
	 */
	CommandEndInvalidationMessages();
}

/*
 *	AtCommit_Memory
 */
static void fc_AtCommit_Memory(void)
{
	/*
	 * 现在我们已经 "退出" 事务，让系统在顶层内存上下文中分配东西，而不是每个事务上下文中。
	 */
	MemoryContextSwitchTo(TopMemoryContext);

	/*
	 * 释放所有事务本地内存。
	 */
	Assert(TopTransactionContext != NULL);
	MemoryContextDelete(TopTransactionContext);
	TopTransactionContext = NULL;
	CurTransactionContext = NULL;
	CurrentTransactionState->curTransactionContext = NULL;
}

/* ----------------------------------------------------------------
 *						CommitSubTransaction 相关
 * ----------------------------------------------------------------
 */

/*
 * AtSubCommit_Memory
 */
static void fc_AtSubCommit_Memory(void)
{
	TransactionState fc_s = CurrentTransactionState;

	Assert(fc_s->parent != NULL);

	/* 返回到父事务级别的内存上下文。 */
	CurTransactionContext = fc_s->parent->curTransactionContext;
	MemoryContextSwitchTo(CurTransactionContext);

	/*
	 * 通常我们不能丢弃子事务的 CurTransactionContext，因为其中包含的数据在上层提交时会需要。然而，如果实际上没有任何内容在其中，我们可以丢弃它。这避免了在 "微不足道" 的子事务的常见情况下出现小内存泄漏。
	 */
	if (MemoryContextIsEmpty(fc_s->curTransactionContext))
	{
		MemoryContextDelete(fc_s->curTransactionContext);
		fc_s->curTransactionContext = NULL;
	}
}

/*
 * AtSubCommit_childXids
 *
 * 将我自己的 XID 和我的子 XIDs 作为已提交的子事务传递给我的父事务。
 */
static void fc_AtSubCommit_childXids(void)
{
	TransactionState fc_s = CurrentTransactionState;
	int			fc_new_nChildXids;

	Assert(fc_s->parent != NULL);

	/*
	 * 父事务的 childXids 数组需要容纳我的 XID 和我所有的 childXids，以及已经存在的 XIDs。
	 */
	fc_new_nChildXids = fc_s->parent->nChildXids + fc_s->nChildXids + 1;

	/* 如有必要，分配或扩大父数组 */
	if (fc_s->parent->maxChildXids < fc_new_nChildXids)
	{
		int			fc_new_maxChildXids;
		TransactionId *fc_new_childXids;

		/*
		 * 现在我们需要的大小翻倍，以避免多次扩大它。
		 * 但我们不能超过 MaxAllocSize。 （后一个限制
		 * 确保我们在这里或计算 new_nChildXids 时不需要担心整数溢出。）
		 */
		fc_new_maxChildXids = Min(fc_new_nChildXids * 2,
							   (int) (MaxAllocSize / sizeof(TransactionId)));

		if (fc_new_maxChildXids < fc_new_nChildXids)
			ereport(ERROR,
					(errcode(ERRCODE_PROGRAM_LIMIT_EXCEEDED),
					 errmsg("maximum number of committed subtransactions (%d) exceeded",
							(int) (MaxAllocSize / sizeof(TransactionId)))));

		/*
		 * 我们将子 XID 数组保存在 TopTransactionContext 中；这避免了
		 * 为可能仅为几字节的孙子 XIDs 设置子事务上下文。
		 */
		if (fc_s->parent->childXids == NULL)
			fc_new_childXids =
				MemoryContextAlloc(TopTransactionContext,
								   fc_new_maxChildXids * sizeof(TransactionId));
		else
			fc_new_childXids = repalloc(fc_s->parent->childXids,
									 fc_new_maxChildXids * sizeof(TransactionId));

		fc_s->parent->childXids = fc_new_childXids;
		fc_s->parent->maxChildXids = fc_new_maxChildXids;
	}

	/*
	 * 将我的所有 XIDs 复制到父数组中。
	 *
	 * 注意：我们依赖于一个事实，即子事务的 XID 始终在其
	 * 父事务的后面。通过在子事务的 XIDs 之前复制此子事务的 XID，
	 * 我们确保数组保持有序。同样，数组中已经存在的所有 XIDs
	 * 都属于在我们之前开始和子提交的子事务，因此它们的 XIDs
	 * 必须早于我们的 XID。
	 */
	fc_s->parent->childXids[fc_s->parent->nChildXids] = XidFromFullTransactionId(fc_s->fullTransactionId);

	if (fc_s->nChildXids > 0)
		memcpy(&fc_s->parent->childXids[fc_s->parent->nChildXids + 1],
			   fc_s->childXids,
			   fc_s->nChildXids * sizeof(TransactionId));

	fc_s->parent->nChildXids = fc_new_nChildXids;

	/* 释放子数组以避免泄漏 */
	if (fc_s->childXids != NULL)
		pfree(fc_s->childXids);
	/* 我们必须重置这些以避免在提交时失败后的双重释放 */
	fc_s->childXids = NULL;
	fc_s->nChildXids = 0;
	fc_s->maxChildXids = 0;
}

/* ----------------------------------------------------------------
 *						AbortTransaction 相关内容
 * ----------------------------------------------------------------
 */

/*
 *	RecordTransactionAbort
 *
 * 返回 xact 及其子事务中的最新 XID，如果 xact 没有 XID 则返回 InvalidTransactionId
 * （我们在这里计算是因为这样更简单。）
 */
static TransactionId fc_RecordTransactionAbort(bool fc_isSubXact)
{
	TransactionId fc_xid = GetCurrentTransactionIdIfAny();
	TransactionId fc_latestXid;
	int			fc_nrels;
	RelFileNode *fc_rels;
	int			fc_ndroppedstats = 0;
	xl_xact_stats_item *fc_droppedstats = NULL;
	int			fc_nchildren;
	TransactionId *fc_children;
	TimestampTz fc_xact_time;

	/*
	 * 如果我们没有被分配 XID，则没人会在乎我们是否中止。
	 * 因此，在这种情况下我们完成了。我们是否有要删除的
	 * rels 并不重要（注意这个例程并不负责实际删除它们）。
	 * 我们也不能有任何子 XIDs。
	 */
	if (!TransactionIdIsValid(fc_xid))
	{
		/* 在下一个事务写入内容之前重置 XactLastRecEnd */
		if (!fc_isSubXact)
			XactLastRecEnd = 0;
		return InvalidTransactionId;
	}

	/*
	 * 我们有一个有效的 XID，因此我们应该为它写入 ABORT 记录。
	 *
	 * 我们在这里不将 XLOG 刷新到磁盘，因为在崩溃后默认假设
	 * 是我们已经中止。出于同样的原因，我们不需要担心与检查点
	 * 启动的互锁问题。
	 */

	/*
	 * 检查我们是否在 RecordTransactionCommit 中 halfway aborted。
	 */
	if (TransactionIdDidCommit(fc_xid))
		elog(PANIC, "cannot abort transaction %u, it was already committed",
			 fc_xid);

	/* 获取我们需要的中止记录的数据 */
	fc_nrels = smgrGetPendingDeletes(false, &fc_rels);
	fc_nchildren = xactGetCommittedChildren(&fc_children);
	fc_ndroppedstats = pgstat_get_transactional_drops(false, &fc_droppedstats);

	/* XXX 我们真的需要一个临界区吗？ */
	START_CRIT_SECTION();

	/* 写入 ABORT 记录 */
	if (fc_isSubXact)
		fc_xact_time = GetCurrentTimestamp();
	else
	{
		fc_SetCurrentTransactionStopTimestamp();
		fc_xact_time = xactStopTimestamp;
	}

	XactLogAbortRecord(fc_xact_time,
					   fc_nchildren, fc_children,
					   fc_nrels, fc_rels,
					   fc_ndroppedstats, fc_droppedstats,
					   MyXactFlags, InvalidTransactionId,
					   NULL);

	/*
	 * 报告最新的异步中止 LSN，以便 WAL 写入器知道
	 * 刷新此中止。迟延这个没有任何好处，因为
	 * WALWriter 也可以在合适的时候进行处理。在流复制中
	 * 这很重要，因为如果我们不定期刷新 WAL，我们将会发现
	 * 大的中止会让我们在提交发生后留下长时间的积压，
	 * 增加在那个时候发生问题时的数据丢失窗口。
	 */
	if (!fc_isSubXact)
		XLogSetAsyncXactLSN(XactLastRecEnd);

	/*
	 * 在 clog 中标记事务已中止。这并不是绝对必要的
	 * 但我们可以在这里做；此外，在 subxact 的情况下
	 * 这很有帮助，因为 XactLockTableWait 利用它来避免
	 * 等待已经中止的子事务。即使没有将 ABORT 记录刷新到磁盘，
	 * 这样做也是可以的，因为在崩溃的情况下
	 * 我们可以假定已经中止。
	 */
	TransactionIdAbortTree(fc_xid, fc_nchildren, fc_children);

	END_CRIT_SECTION();

	/* 在我们有子 XIDs 方便时计算 latestXid */
	fc_latestXid = TransactionIdLatest(fc_xid, fc_nchildren, fc_children);

	/*
	 * 如果我们正在中止一个子事务，我们可以立即从 PGPROC 的
	 * 正在运行的子 XID 缓存中移除失败的 XID。我们在这里为
	 * subxacts 做了这个，因为我们已经有了子 XID 数组。对于
	 * 主事务，相应的操作在此函数返回后立即发生。
	 */
	if (fc_isSubXact)
		XidCacheRemoveRunningXids(fc_xid, fc_nchildren, fc_children, fc_latestXid);

	/* 在下一个事务写入内容之前重置 XactLastRecEnd */
	if (!fc_isSubXact)
		XactLastRecEnd = 0;

	/* 并清理本地数据 */
	if (fc_rels)
		pfree(fc_rels);
	if (fc_ndroppedstats)
		pfree(fc_droppedstats);

	return fc_latestXid;
}

/*
 *	AtAbort_Memory
 */
static void fc_AtAbort_Memory(void)
{
	/*
	 * 切换到 TransactionAbortContext，即使其他地方没有
	 * 空余空间，它也应该有一些。我们将在这个上下文中工作，
	 * 直到完成清理。
	 *
	 * 如果我们无法创建 TransactionAbortContext，这里几乎不可能到达；
	 * 如果是这样，使用 TopMemoryContext。
	 */
	if (TransactionAbortContext != NULL)
		MemoryContextSwitchTo(TransactionAbortContext);
	else
		MemoryContextSwitchTo(TopMemoryContext);
}

/*
 * AtSubAbort_Memory
 */
static void fc_AtSubAbort_Memory(void)
{
	Assert(TransactionAbortContext != NULL);

	MemoryContextSwitchTo(TransactionAbortContext);
}


/*
 *	AtAbort_ResourceOwner
 */
static void fc_AtAbort_ResourceOwner(void)
{
	/*
	 * 确保我们有一个有效的 ResourceOwner，如果可能的话（否则它将是
	 * NULL，这也是可以的）
	 */
	CurrentResourceOwner = TopTransactionResourceOwner;
}

/*
 * AtSubAbort_ResourceOwner
 */
static void fc_AtSubAbort_ResourceOwner(void)
{
	TransactionState fc_s = CurrentTransactionState;

	/* 确保我们有一个有效的 ResourceOwner */
	CurrentResourceOwner = fc_s->curTransactionOwner;
}


/*
 * AtSubAbort_childXids
 */
static void fc_AtSubAbort_childXids(void)
{
	TransactionState fc_s = CurrentTransactionState;

	/*
	 * 我们在 TopTransactionContext 中保留 child-XID 数组（见
	 * AtSubCommit_childXids）。这意味着我们最好在中止时显式释放数组
	 * 以避免内存泄漏。
	 */
	if (fc_s->childXids != NULL)
		pfree(fc_s->childXids);
	fc_s->childXids = NULL;
	fc_s->nChildXids = 0;
	fc_s->maxChildXids = 0;

	/*
	 * 我们可以在这里修剪 unreportedXids 数组。但我们不打算这样做。
	 * 那可能会减少 XLOG_XACT_ASSIGNMENT 记录的数量，但这可能会
	 * 在更常见的路径中引入更多的 CPU 时间，因此我们选择不这样做。
	 */
}

/* ----------------------------------------------------------------
 *						CleanupTransaction stuff
 * ----------------------------------------------------------------
 */

/*
 *	AtCleanup_Memory
 */
static void fc_AtCleanup_Memory(void)
{
	Assert(CurrentTransactionState->parent == NULL);

	/*
	 * 现在我们已经 "退出" 事务，让系统在顶层内存上下文中分配东西，而不是每个事务上下文中。
	 */
	MemoryContextSwitchTo(TopMemoryContext);

	/*
	 * 清理特殊的中止上下文以备下次使用。
	 */
	if (TransactionAbortContext != NULL)
		MemoryContextResetAndDeleteChildren(TransactionAbortContext);

	/*
	 * 释放所有事务本地内存。
	 */
	if (TopTransactionContext != NULL)
		MemoryContextDelete(TopTransactionContext);
	TopTransactionContext = NULL;
	CurTransactionContext = NULL;
	CurrentTransactionState->curTransactionContext = NULL;
}


/* ----------------------------------------------------------------
 *						CleanupSubTransaction stuff
 * ----------------------------------------------------------------
 */

/*
 * AtSubCleanup_Memory
 */
static void fc_AtSubCleanup_Memory(void)
{
	TransactionState fc_s = CurrentTransactionState;

	Assert(fc_s->parent != NULL);

	/* 确保我们不在即将删除的上下文中 */
	MemoryContextSwitchTo(fc_s->parent->curTransactionContext);
	CurTransactionContext = fc_s->parent->curTransactionContext;

	/*
	 * 清理特殊的中止上下文以备下次使用。
	 */
	if (TransactionAbortContext != NULL)
		MemoryContextResetAndDeleteChildren(TransactionAbortContext);

	/*
	 * 删除子事务的本地内存上下文。它的 CurTransactionContext 也可以
	 * 被删除（请注意，这也会导致子事务的任何子事务的 CurTransactionContexts 被删除）。
	 */
	if (fc_s->curTransactionContext)
		MemoryContextDelete(fc_s->curTransactionContext);
	fc_s->curTransactionContext = NULL;
}

/* ----------------------------------------------------------------
 *						接口例程
 * ----------------------------------------------------------------
 */

/*
 *	StartTransaction
 */
static void fc_StartTransaction(void)
{
	TransactionState fc_s;
	VirtualTransactionId fc_vxid;

	/*
	 * 让我们确保状态堆栈是空的
	 */
	fc_s = &TopTransactionStateData;
	CurrentTransactionState = fc_s;

	Assert(!FullTransactionIdIsValid(XactTopFullTransactionId));

	/* 检查当前事务状态 */
	Assert(fc_s->state == TRANS_DEFAULT);

	/*
	 * 在启动处理期间适当地设置当前事务状态信息。
	 * 请注意，一旦事务状态被切换，这个过程在获取用户 ID 和安全上下文
	 * 标志之前是不能失败的。
	 */
	fc_s->state = TRANS_START;
	fc_s->fullTransactionId = InvalidFullTransactionId;	/* 直到分配 */

	/* 确定语句是否在此事务中被记录 */
	xact_is_sampled = log_xact_sample_rate != 0 &&
		(log_xact_sample_rate == 1 ||
		 pg_prng_double(&pg_global_prng_state) <= log_xact_sample_rate);

	/*
	 * 初始化当前事务状态字段
	 *
	 * 注意：prevXactReadOnly 在最外层不使用
	 */
	fc_s->nestingLevel = 1;
	fc_s->gucNestLevel = 1;
	fc_s->childXids = NULL;
	fc_s->nChildXids = 0;
	fc_s->maxChildXids = 0;

	/*
	 * 一旦获取当前用户 ID 和安全上下文标志，两个都将被适当地重置，
	 * 即使事务启动失败。
	 */
	GetUserIdAndSecContext(&fc_s->prevUser, &fc_s->prevSecContext);

	/* SecurityRestrictionContext 在事务外部不应被设置 */
	Assert(fc_s->prevSecContext == 0);

	/*
	 * 确保我们已重置事务状态变量
	 *
	 * 如果恢复仍在进行中，则将此事务标记为只读。
	 * 我们在 XLogInsert 和其他地方有较低级别的防御来阻止我们在恢复期间
	 * 修改数据，但这为用户提供了事务为只读的正常指示。
	 */
	if (RecoveryInProgress())
	{
		fc_s->startedInRecovery = true;
		XactReadOnly = true;
	}
	else
	{
		fc_s->startedInRecovery = false;
		XactReadOnly = DefaultXactReadOnly;
	}
	XactDeferrable = DefaultXactDeferrable;
	XactIsoLevel = DefaultXactIsoLevel;
	forceSyncCommit = false;
	MyXactFlags = 0;

	/*
	 * 重新初始化事务内计数器
	 */
	fc_s->subTransactionId = TopSubTransactionId;
	currentSubTransactionId = TopSubTransactionId;
	currentCommandId = FirstCommandId;
	currentCommandIdUsed = false;

	/*
	 * 初始化报告的 xid 记账
	 */
	nUnreportedXids = 0;
	fc_s->didLogXid = false;

	/*
	 * 必须首先初始化资源管理的东西
	 */
	fc_AtStart_Memory();
	fc_AtStart_ResourceOwner();

	/*
	 * 分配一个新的 LocalTransactionId，并将其与 backendId 结合
	 * 形成一个虚拟事务 id。
	 */
	fc_vxid.backendId = MyBackendId;
	fc_vxid.localTransactionId = GetNextLocalTransactionId();

	/*
	 * 在我们在 proc 数组中宣布它之前锁定虚拟事务 id
	 */
	VirtualXactLockTableInsert(fc_vxid);

	/*
	 * 在 proc 数组中进行宣传。我们假设 localTransactionId 的分配是原子性的，
	 * 而 backendId 应该已经被设置。
	 */
	Assert(MyProc->backendId == fc_vxid.backendId);
	MyProc->lxid = fc_vxid.localTransactionId;

	TRACE_POSTGRESQL_TRANSACTION_START(vxid.localTransactionId);

	/*
	 * 设置 transaction_timestamp()（也称为 now()）。通常，我们希望
	 * 这与第一个命令的 statement_timestamp() 相同，因此不做新的
	 * GetCurrentTimestamp() 调用（那样会很昂贵）。但是，对于在过程中
	 * 启动的事务（即，非原子 SPI 上下文），我们确实需要推进时间戳。
	 * 另外，在并行工作者中，时间戳应该已经通过调用
	 * SetParallelStartTimestamps() 提供。
	 */
	if (!IsParallelWorker())
	{
		if (!SPI_inside_nonatomic_context())
			xactStartTimestamp = stmtStartTimestamp;
		else
			xactStartTimestamp = GetCurrentTimestamp();
	}
	else
		Assert(xactStartTimestamp != 0);
	pgstat_report_xact_timestamp(xactStartTimestamp);
	/* 将 xactStopTimestamp 标记为未设置。 */
	xactStopTimestamp = 0;

	/*
	 * 为新事务初始化其他子系统
	 */
	AtStart_GUC();
	fc_AtStart_Cache();
	AfterTriggerBeginXact();

	/*
	 * 完成启动处理，将当前事务状态设置为“进行中”
	 */
	fc_s->state = TRANS_INPROGRESS;

	fc_ShowTransactionState("StartTransaction");
}


/*
 *	CommitTransaction
 *
 * 注意：如果您更改此例程，最好也查看 PrepareTransaction！
 */
static void fc_CommitTransaction(void)
{
	TransactionState fc_s = CurrentTransactionState;
	TransactionId fc_latestXid;
	bool		fc_is_parallel_worker;

	fc_is_parallel_worker = (fc_s->blockState == TBLOCK_PARALLEL_INPROGRESS);

	/* 在并行工作者提交期间强制执行并行模式限制。 */
	if (fc_is_parallel_worker)
		EnterParallelMode();

	fc_ShowTransactionState("CommitTransaction");

	/*
	 * 检查当前事务状态
	 */
	if (fc_s->state != TRANS_INPROGRESS)
		elog(WARNING, "CommitTransaction while in %s state",
			 fc_TransStateAsString(fc_s->state));
	Assert(fc_s->parent == NULL);

	/*
	 * 执行涉及调用用户定义代码的预提交处理，例如触发器。
	 * SECURITY_RESTRICTED_OPERATION 上下文不能排队
	 * 在这里运行的操作，因为这将绕过沙盒。
	 * 由于关闭游标可能会排队触发器操作，触发器可能会打开
	 * 游标等，因此我们必须不断循环，直到没有事情可做。
	 */
	for (;;)
	{
		/*
		 * 触发所有当前待处理的延迟触发器。
		 */
		AfterTriggerFireDeferred();

		/*
		 * 关闭打开的门户（将可保留的门户转换为静态门户）。
		 * 如果没有打开的门户，我们就完成了……否则回到检查
		 * 它们是否排队了延迟触发器。涂抹、冲洗、重复。
		 */
		if (!PreCommit_Portals(false))
			break;
	}

	/*
	 * 剩余的操作不能调用任何用户定义的代码，因此可以安全
	 * 地开始关闭事务内服务。但请注意，大多数
	 * 这些操作仍然可能抛出错误，这将使我们切换到
	 * 事务中止路径。
	 */

	fc_CallXactCallbacks(fc_is_parallel_worker ? XACT_EVENT_PARALLEL_PRE_COMMIT
					  : XACT_EVENT_PRE_COMMIT);

	/* 如果我们可能有并行工作者，现在清理它们。 */
	if (IsInParallelMode())
		AtEOXact_Parallel(true);

	/* 关闭延迟触发器管理器 */
	AfterTriggerEndXact(true);

	/*
	 * 让 ON COMMIT 管理进行处理（必须在关闭
	 * 游标后发生，以避免悬挂引用问题）
	 */
	PreCommit_on_commit_actions();

	/*
	 * 同步在此事务中创建且未记录到 WAL 的文件。这
	 * 必须在 AtEOXact_RelationMap() 之前发生，以便我们
	 * 不会在崩溃后看到已提交但破损的文件。
	 */
	smgrDoPendingSyncs(true, fc_is_parallel_worker);

	/* 在较低级别清理之前关闭大型对象 */
	AtEOXact_LargeObject(true);

	/*
	 * 将 NOTIFY 命令发送的通知插入队列。这
	 * 应该在预提交序列的后期进行，以最小化持有
	 * 通知插入锁的时间。然而，这可能会导致
	 * 创建快照，因此我们必须在可串行化清理之前执行此操作。
	 */
	PreCommit_Notify();

	/*
	 * 将可串行化事务标记为已完成，以便进行谓词锁定。
	 * 这应该在尽可能晚的时候进行，但仍然允许
	 * 在提交时发现的失败模式引发错误。然而，这在
	 * 并行工作者中是不合适的，因为我们没有提交
	 * 领导者的事务，并且其可串行化状态将继续存在。
	 */
	if (!fc_is_parallel_worker)
		PreCommit_CheckForSerializationFailure();

	/* 在清理时防止取消/死亡中断 */
	HOLD_INTERRUPTS();

	/* 将更新提交到关系映射——尽可能晚地进行此操作 */
	AtEOXact_RelationMap(true, fc_is_parallel_worker);

	/*
	 * 在提交处理期间适当地设置当前事务状态信息
	 */
	fc_s->state = TRANS_COMMIT;
	fc_s->parallelModeLevel = 0;

	if (!fc_is_parallel_worker)
	{
		/*
		 * 我们需要在 pg_xact 中将我们的 XIDs 标记为已提交。这是我们
		 * 持久提交的地方。
		 */
		fc_latestXid = fc_RecordTransactionCommit();
	}
	else
	{
		/*
		 * 我们不得标记我们的 XID 为已提交；并行领导者负责这件事。
		 */
		fc_latestXid = InvalidTransactionId;

		/*
		 * 确保领导者会知道在它提交之前我们写的任何 WAL。
		 */
		ParallelWorkerReportLastRecEnd(XactLastRecEnd);
	}

	TRACE_POSTGRESQL_TRANSACTION_COMMIT(MyProc->lxid);

	/*
	 * 让其他人知道我没有正在进行的事务。请注意，这
	 * 必须在释放我们持有的锁之前和
	 * RecordTransactionCommit 之后进行。
	 */
	ProcArrayEndTransaction(MyProc, fc_latestXid);

	/*
	 * 这是所有提交后的清理。注意，如果在这里出现错误，
	 * 执行事务的时机已太晚，无法中止事务。这里应该只是
	 * 非关键的资源释放。
	 *
	 * 操作的顺序并不是完全随机的。思路是：
	 * 释放其他后端可见的资源（例如，文件，缓冲区锁定）；
	 * 然后释放锁；然后释放后端本地资源。我们希望
	 * 在任何等待我们的后端看到我们的事务已完成清理的时刻释放锁。
	 *
	 * 可以与单个查询关联的资源由
	 * ResourceOwner机制处理。这里的其他调用用于后端范围
	 * 的状态。
	 */

	fc_CallXactCallbacks(fc_is_parallel_worker ? XACT_EVENT_PARALLEL_COMMIT
					  : XACT_EVENT_COMMIT);

	ResourceOwnerRelease(TopTransactionResourceOwner,
						 RESOURCE_RELEASE_BEFORE_LOCKS,
						 true, true);

	/* 检查我们是否释放了所有的缓冲区锁定 */
	AtEOXact_Buffers(true);

	/* 清理关系缓存 */
	AtEOXact_RelationCache(true);

	/*
	 * 使目录更改对所有后端可见。这必须在
	 * 释放relcache引用后进行（参见
	 * AtEOXact_RelationCache的注释），但在释放锁之前（如果有人
	 * 在等待我们修改的关系的锁，我们希望他们在开始使用该关系之前
	 * 知道目录更改）。
	 */
	AtEOXact_Inval(true);

	AtEOXact_MultiXact();

	ResourceOwnerRelease(TopTransactionResourceOwner,
						 RESOURCE_RELEASE_LOCKS,
						 true, true);
	ResourceOwnerRelease(TopTransactionResourceOwner,
						 RESOURCE_RELEASE_AFTER_LOCKS,
						 true, true);

	/*
	 * 同样，在释放relcache和缓冲区锁定之后删除事务中删除的文件是最好的。
	 * （在提交期间这并不是严格必要的，因为这样的锁定应该已经被释放，
	 * 但在中止期间这种顺序绝对是关键的。）由于
	 * 这可能需要很多秒，因此也要延迟到释放锁之后。
	 * 其他后端将观察到相关的目录更改，而不会尝试访问受影响的文件。
	 */
	smgrDoPendingDeletes(true);

	/*
	 * 向其他后端发送通知信号（并进行其他
	 * 提交后的NOTIFY清理）。这必须在我们的
	 * 事务从其他后端的角度完全完成之后才能发生。
	 */
	AtCommit_Notify();

	/*
	 * 在这之后的一切应该是纯粹的内部到这个后端的
	 * 清理。
	 */
	AtEOXact_GUC(true, 1);
	AtEOXact_SPI(true);
	AtEOXact_Enum();
	AtEOXact_on_commit_actions(true);
	AtEOXact_Namespace(true, fc_is_parallel_worker);
	AtEOXact_SMgr();
	AtEOXact_Files(true);
	AtEOXact_ComboCid();
	AtEOXact_HashTables(true);
	AtEOXact_PgStat(true, fc_is_parallel_worker);
	AtEOXact_Snapshot(true, false);
	AtEOXact_ApplyLauncher(true);
	pgstat_report_xact_timestamp(0);

	CurrentResourceOwner = NULL;
	ResourceOwnerDelete(TopTransactionResourceOwner);
	fc_s->curTransactionOwner = NULL;
	CurTransactionResourceOwner = NULL;
	TopTransactionResourceOwner = NULL;

	fc_AtCommit_Memory();

	fc_s->fullTransactionId = InvalidFullTransactionId;
	fc_s->subTransactionId = InvalidSubTransactionId;
	fc_s->nestingLevel = 0;
	fc_s->gucNestLevel = 0;
	fc_s->childXids = NULL;
	fc_s->nChildXids = 0;
	fc_s->maxChildXids = 0;

	XactTopFullTransactionId = InvalidFullTransactionId;
	nParallelCurrentXids = 0;

	/*
	 * 完成提交处理，当前事务状态重置为
	 * 默认状态
	 */
	fc_s->state = TRANS_DEFAULT;

	RESUME_INTERRUPTS();
}


/*
 *	准备事务
 *
 * 注意：如果您更改此例程，最好也查看CommitTransaction！
 */
static void fc_PrepareTransaction(void)
{
	TransactionState fc_s = CurrentTransactionState;
	TransactionId fc_xid = GetCurrentTransactionId();
	GlobalTransaction fc_gxact;
	TimestampTz fc_prepared_at;

	Assert(!IsInParallelMode());

	fc_ShowTransactionState("PrepareTransaction");

	/*
	 * 检查当前事务状态
	 */
	if (fc_s->state != TRANS_INPROGRESS)
		elog(WARNING, "PrepareTransaction while in %s state",
			 fc_TransStateAsString(fc_s->state));
	Assert(fc_s->parent == NULL);

	/*
	 * 执行涉及调用用户定义代码的预提交处理，例如
	 * 触发器。由于关闭游标可能会排队触发器操作，
	 * 触发器可能会打开游标等，因此我们必须不断循环，直到
	 * 没有其他操作要执行。
	 */
	for (;;)
	{
		/*
		 * 触发所有当前待处理的延迟触发器。
		 */
		AfterTriggerFireDeferred();

		/*
		 * 关闭打开的门户（将可保留的门户转换为静态门户）。
		 * 如果没有打开的门户，我们就完成了……否则回到检查
		 * 它们是否排队了延迟触发器。涂抹、冲洗、重复。
		 */
		if (!PreCommit_Portals(true))
			break;
	}

	fc_CallXactCallbacks(XACT_EVENT_PRE_PREPARE);

	/*
	 * 剩余的操作不能调用任何用户定义的代码，因此可以安全
	 * 地开始关闭事务内服务。但请注意，大多数
	 * 这些操作仍然可能抛出错误，这将使我们切换到
	 * 事务中止路径。
	 */

	/* 关闭延迟触发器管理器 */
	AfterTriggerEndXact(true);

	/*
	 * 让 ON COMMIT 管理进行处理（必须在关闭
	 * 游标后发生，以避免悬挂引用问题）
	 */
	PreCommit_on_commit_actions();

	/*
	 * 同步在此事务中创建但未记录到 WAL 的文件。这必须在
	 * EndPrepare() 之前进行，以便我们在崩溃和 COMMIT PREPARED
	 * 后不看到承诺但损坏的文件。
	 */
	smgrDoPendingSyncs(true, false);

	/* 在较低级别清理之前关闭大型对象 */
	AtEOXact_LargeObject(true);

	/* NOTIFY 在此时不需要任何工作 */

	/*
	 * 将可序列化事务标记为完成，以便进行谓词锁定。
	 * 这应该尽可能晚进行，同时仍允许在提交时对失败模式
	 * 报告错误。
	 */
	PreCommit_CheckForSerializationFailure();

	/*
	 * 如果我们在此事务中访问了临时表，则不允许 PREPARE TRANSACTION。
	 * 让准备好的事务在另一个后端的临时表上持有锁似乎是个坏主意
	 * --- 例如，这将阻止后端退出。还有其他问题，例如如何清理
	 * 源后端的本地缓冲区和 ON COMMIT 状态，如果准备好的事务包含
	 * 删除临时表的操作。
	 *
	 * 其他对象类型，如函数、运算符或扩展，与它们的创建、锁定或
	 * 删除应有相同的限制，因为这可能会干扰此会话或甚至后续尝试
	 * 使用相同临时命名空间的会话。
	 *
	 * 我们必须在执行任何 ON COMMIT 操作后检查这一点，因为它们
	 * 仍可能访问临时关系。
	 *
	 * XXX 从原则上讲，这可以放宽以允许一些有用的特例，例如
	 * 在事务内创建和删除的临时表。不过，这似乎需要更多的记账。
	 */
	if ((MyXactFlags & XACT_FLAGS_ACCESSEDTEMPNAMESPACE))
		ereport(ERROR,
				(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
				 errmsg("cannot PREPARE a transaction that has operated on temporary objects")));

	/*
	 * 同样，不允许在 pg_export_snapshot 后进行 PREPARE。
	 * 如果我们在 twophase.c 中添加清理逻辑，这可以得到支持，
	 * 但目前看来不值得为此麻烦。
	 */
	if (XactHasExportedSnapshots())
		ereport(ERROR,
				(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
				 errmsg("cannot PREPARE a transaction that has exported snapshots")));

	/* 在清理时防止取消/死亡中断 */
	HOLD_INTERRUPTS();

	/*
	 * 在准备处理期间适当地设置当前事务状态信息
	 */
	fc_s->state = TRANS_PREPARE;

	fc_prepared_at = GetCurrentTimestamp();

	/* 告诉 bufmgr 和 smgr 准备提交 */
	BufmgrCommit();

	/*
	 * 为此事务保留 GID。如果请求的 GID 无效或已在使用中，
	 * 可能会失败。
	 */
	fc_gxact = MarkAsPreparing(fc_xid, prepareGID, fc_prepared_at,
							GetUserId(), MyDatabaseId);
	prepareGID = NULL;

	/*
	 * 收集2PC状态文件的数据。请注意，通常在此步骤中被调用模块不应发生实际状态更改，因为在提交之前仍然可能出现故障，在这种情况下我们希望事务中止能够进行清理。（尤其是，AtPrepare例程如果发现无法处理的情况可能会出错。）状态清理应在下面的PostPrepare例程中进行。然而，一些模块可以在这里先清除状态，因为它们在中止时无论如何也不会对其进行操作。
	 *
	 * 注意：由于2PC状态文件记录将以相同的顺序重放，因此这些调用的顺序必须与我们希望在COMMIT PREPARED或ROLLBACK PREPARED期间发生的顺序匹配；特别要注意事情应该在释放事务锁之前还是之后发生。
	 */
	StartPrepare(fc_gxact);

	AtPrepare_Notify();
	AtPrepare_Locks();
	AtPrepare_PredicateLocks();
	AtPrepare_PgStat();
	AtPrepare_MultiXact();
	AtPrepare_RelationMap();

	/*
	 * 这里才是真正的准备。
	 *
	 * 即使我们没有进行任何更新，我们也必须记录事务准备，因为如果我们丢失一个全局事务，事务管理器可能会感到困惑。
	 */
	EndPrepare(fc_gxact);

	/*
	 * 现在我们清理后端内部状态并释放内部资源。
	 */

	/* 在下一个事务写入内容之前重置 XactLastRecEnd */
	XactLastRecEnd = 0;

	/*
	 * 将我们的锁转移到一个虚拟的PGPROC。这必须在ProcArrayClearTransaction()之前完成。否则，GetLockConflicts()将得出“事务已经提交或中止”的结论。
	 */
	PostPrepare_Locks(fc_xid);

	/*
	 * 让其他人知道没有我正在进行的事务。这必须在准备好的事务被标记为有效之后完成，否则有人可能会认为它是解锁和可回收的。
	 */
	ProcArrayClearTransaction(MyProc);

	/*
	 * 在正常的提交处理过程中，这都是非关键的后事务清理。然而，当事务被准备时，重要的是将锁和其他每个后端资源转移到准备好的事务的PGPROC条目中。请注意，如果在这里引发错误，已经太晚无法中止事务。XXX：这可能应该在一个关键部分中，以强制在这些操作中任何一个失败时触发PANIC，但是这种补救措施可能比疾病更糟。
	 */

	fc_CallXactCallbacks(XACT_EVENT_PREPARE);

	ResourceOwnerRelease(TopTransactionResourceOwner,
						 RESOURCE_RELEASE_BEFORE_LOCKS,
						 true, true);

	/* 检查我们是否释放了所有的缓冲区锁定 */
	AtEOXact_Buffers(true);

	/* 清理关系缓存 */
	AtEOXact_RelationCache(true);

	/* 通知不需要postprepare调用 */

	PostPrepare_PgStat();

	PostPrepare_Inval();

	PostPrepare_smgr();

	PostPrepare_MultiXact(fc_xid);

	PostPrepare_PredicateLocks(fc_xid);

	ResourceOwnerRelease(TopTransactionResourceOwner,
						 RESOURCE_RELEASE_LOCKS,
						 true, true);
	ResourceOwnerRelease(TopTransactionResourceOwner,
						 RESOURCE_RELEASE_AFTER_LOCKS,
						 true, true);

	/*
	 * 允许另一个后端完成事务。在PostPrepare_Twophase()之后，事务完全与我们的后端脱离。其余部分只是对后端本地状态的非关键清理。
	 */
	PostPrepare_Twophase();

	/* 在GUC方面，PREPARE与COMMIT的行为相同 */
	AtEOXact_GUC(true, 1);
	AtEOXact_SPI(true);
	AtEOXact_Enum();
	AtEOXact_on_commit_actions(true);
	AtEOXact_Namespace(true, false);
	AtEOXact_SMgr();
	AtEOXact_Files(true);
	AtEOXact_ComboCid();
	AtEOXact_HashTables(true);
	/* 不要在这里调用AtEOXact_PgStat；我们在上面已经修复了pgstat状态 */
	AtEOXact_Snapshot(true, true);
	pgstat_report_xact_timestamp(0);

	CurrentResourceOwner = NULL;
	ResourceOwnerDelete(TopTransactionResourceOwner);
	fc_s->curTransactionOwner = NULL;
	CurTransactionResourceOwner = NULL;
	TopTransactionResourceOwner = NULL;

	fc_AtCommit_Memory();

	fc_s->fullTransactionId = InvalidFullTransactionId;
	fc_s->subTransactionId = InvalidSubTransactionId;
	fc_s->nestingLevel = 0;
	fc_s->gucNestLevel = 0;
	fc_s->childXids = NULL;
	fc_s->nChildXids = 0;
	fc_s->maxChildXids = 0;

	XactTopFullTransactionId = InvalidFullTransactionId;
	nParallelCurrentXids = 0;

	/*
	 * 完成第一个阶段的提交处理，将当前事务状态恢复到默认状态
	 */
	fc_s->state = TRANS_DEFAULT;

	RESUME_INTERRUPTS();
}


/*
 *	中止事务
 */
static void fc_AbortTransaction(void)
{
	TransactionState fc_s = CurrentTransactionState;
	TransactionId fc_latestXid;
	bool		fc_is_parallel_worker;

	/* 在清理时防止取消/死亡中断 */
	HOLD_INTERRUPTS();

	/* 确保我们有一个有效的内存上下文和资源所有者 */
	fc_AtAbort_Memory();
	fc_AtAbort_ResourceOwner();

	/*
	 * 尽快释放我们可能持有的任何LW锁。
	 * （然而，常规锁必须保持到我们完成中止为止。）
	 * 释放LW锁至关重要，因为我们可能在清理时尝试再次获取它们！
	 */
	LWLockReleaseAll();

	/* 清除等待信息和命令进度指示器 */
	pgstat_report_wait_end();
	pgstat_progress_end_command();

	/* 清理缓冲区I/O和缓冲区上下文锁 */
	AbortBufferIO();
	UnlockBuffers();

	/* 重置WAL记录构造状态 */
	XLogResetInsertion();

	/* 取消条件变量休眠 */
	ConditionVariableCancelSleep();

	/*
	 * 还要清理任何等待锁，因为如果我们在这样做之前尝试等待另一个锁，
	 * 锁管理器会出现问题。
	 */
	LockErrorCleanup();

	/*
	 * 如果还有任何超时事件处于活动状态，请确保计划超时中断。
	 * 这涵盖了由于从SIGINT处理程序中longjmp而可能导致的超时中断丢失
	 * （请参阅handle_sig_alarm中的注释）。
	 * 我们将其延迟到LockErrorCleanup之后，以便不无谓地重新调度锁或死锁检查超时。
	 */
	reschedule_timeouts();

	/*
	 * 重新启用信号，以防我们通过从信号处理程序longjmp而到达这里。
	 * 我们在这个序列中相对较早地进行此操作，以便在中止时如有需要，
	 * 超时基础设施将能够正常工作。
	 */
	PG_SETMASK(&UnBlockSig);

	/*
	 * 检查当前事务状态
	 */
	fc_is_parallel_worker = (fc_s->blockState == TBLOCK_PARALLEL_INPROGRESS);
	if (fc_s->state != TRANS_INPROGRESS && fc_s->state != TRANS_PREPARE)
		elog(WARNING, "AbortTransaction while in %s state",
			 fc_TransStateAsString(fc_s->state));
	Assert(fc_s->parent == NULL);

	/*
	 * 在中止处理过程中适当地设置当前事务状态信息
	 */
	fc_s->state = TRANS_ABORT;

	/*
	 * 重置可能已临时改变的用户ID。
	 * 我们需要这个以便在控制从SECURITY DEFINER函数或其他本地更改
	 * CurrentUserId中逃逸时进行清理；因此，SecurityRestrictionContext的
	 * 先前值也需要被恢复。
	 *
	 * （注意：这里不需要恢复会话授权或角色设置，
	 * 因为这些只能通过GUC更改，而GUC会在必要时处理回滚。）
	 */
	SetUserIdAndSecContext(fc_s->prevUser, fc_s->prevSecContext);

	/* 忘记任何活动的REINDEX。 */
	ResetReindexState(fc_s->nestingLevel);

	/* 重置逻辑流状态。 */
	ResetLogicalStreamingState();

	/* 重置快照导出状态。 */
	SnapBuildResetExportedSnapshotState();

	/* 如果处于并行模式，清理工作者并退出并行模式。 */
	if (IsInParallelMode())
	{
		AtEOXact_Parallel(false);
		fc_s->parallelModeLevel = 0;
	}

	/*
	 * 进行中止处理
	 */
	AfterTriggerEndXact(false); /* 'false'意味着中止 */
	AtAbort_Portals();
	smgrDoPendingSyncs(false, fc_is_parallel_worker);
	AtEOXact_LargeObject(false);
	AtAbort_Notify();
	AtEOXact_RelationMap(false, fc_is_parallel_worker);
	AtAbort_Twophase();

	/*
	 * 在pg_xact中公布我们已中止的事实（假设我们已经分配了一个
	 * XID来公布）。但如果我们在并行工作者中，则跳过此步骤；
	 * 用户后端必须写入中止记录。
	 */
	if (!fc_is_parallel_worker)
		fc_latestXid = fc_RecordTransactionAbort(false);
	else
	{
		fc_latestXid = InvalidTransactionId;

		/*
		 * 由于在这种情况下并行领导者不会获得我们的XactLastRecEnd值，
		 * 我们在这种情况下自己推动WAL写入器。
		 * 请参阅RecordTransactionAbort中的相关注释，了解这为什么重要。
		 */
		XLogSetAsyncXactLSN(XactLastRecEnd);
	}

	TRACE_POSTGRESQL_TRANSACTION_ABORT(MyProc->lxid);

	/*
	 * 让其他人知道我没有进行中的事务。注意，这必须在
	 * 释放我们持有的锁之前和在
	 * RecordTransactionAbort之后完成。
	 */
	ProcArrayEndTransaction(MyProc, fc_latestXid);

	/*
	 * 事务终止后的清理。请参见 CommitTransaction() 中关于
	 * 顺序的说明。如果事务在创建资源所有者之前失败，
	 * 我们可以跳过所有步骤。
	 */
	if (TopTransactionResourceOwner != NULL)
	{
		if (fc_is_parallel_worker)
			fc_CallXactCallbacks(XACT_EVENT_PARALLEL_ABORT);
		else
			fc_CallXactCallbacks(XACT_EVENT_ABORT);

		ResourceOwnerRelease(TopTransactionResourceOwner,
							 RESOURCE_RELEASE_BEFORE_LOCKS,
							 false, true);
		AtEOXact_Buffers(false);
		AtEOXact_RelationCache(false);
		AtEOXact_Inval(false);
		AtEOXact_MultiXact();
		ResourceOwnerRelease(TopTransactionResourceOwner,
							 RESOURCE_RELEASE_LOCKS,
							 false, true);
		ResourceOwnerRelease(TopTransactionResourceOwner,
							 RESOURCE_RELEASE_AFTER_LOCKS,
							 false, true);
		smgrDoPendingDeletes(false);

		AtEOXact_GUC(false, 1);
		AtEOXact_SPI(false);
		AtEOXact_Enum();
		AtEOXact_on_commit_actions(false);
		AtEOXact_Namespace(false, fc_is_parallel_worker);
		AtEOXact_SMgr();
		AtEOXact_Files(false);
		AtEOXact_ComboCid();
		AtEOXact_HashTables(false);
		AtEOXact_PgStat(false, fc_is_parallel_worker);
		AtEOXact_ApplyLauncher(false);
		pgstat_report_xact_timestamp(0);
	}

	/*
	 * 状态保持为 TRANS_ABORT，直到 CleanupTransaction() 被调用。
	 */
	RESUME_INTERRUPTS();
}

/*
 *	CleanupTransaction
 */
static void fc_CleanupTransaction(void)
{
	TransactionState fc_s = CurrentTransactionState;

	/*
	 * 状态应该仍然是由 AbortTransaction() 设置的 TRANS_ABORT。
	 */
	if (fc_s->state != TRANS_ABORT)
		elog(FATAL, "CleanupTransaction: unexpected state %s",
			 fc_TransStateAsString(fc_s->state));

	/*
	 * 执行终止清理处理
	 */
	AtCleanup_Portals();		/* 现在可以安全地释放门户内存 */
	AtEOXact_Snapshot(false, true); /* 并释放事务的快照 */

	CurrentResourceOwner = NULL;	/* 和资源所有者 */
	if (TopTransactionResourceOwner)
		ResourceOwnerDelete(TopTransactionResourceOwner);
	fc_s->curTransactionOwner = NULL;
	CurTransactionResourceOwner = NULL;
	TopTransactionResourceOwner = NULL;

	fc_AtCleanup_Memory();			/* 和事务内存 */

	fc_s->fullTransactionId = InvalidFullTransactionId;
	fc_s->subTransactionId = InvalidSubTransactionId;
	fc_s->nestingLevel = 0;
	fc_s->gucNestLevel = 0;
	fc_s->childXids = NULL;
	fc_s->nChildXids = 0;
	fc_s->maxChildXids = 0;
	fc_s->parallelModeLevel = 0;

	XactTopFullTransactionId = InvalidFullTransactionId;
	nParallelCurrentXids = 0;

	/*
	 * 完成终止处理，将当前事务状态恢复为
	 * 默认状态
	 */
	fc_s->state = TRANS_DEFAULT;
}

/*
 *	StartTransactionCommand
 */
void StartTransactionCommand(void)
{
	TransactionState fc_s = CurrentTransactionState;

	switch (fc_s->blockState)
	{
			/*
			 * 如果我们不在事务块中，我们就执行我们通常的开始
			 * 事务。
			 */
		case TBLOCK_DEFAULT:
			fc_StartTransaction();
			fc_s->blockState = TBLOCK_STARTED;
			break;

			/*
			 * 我们在某个事务块或子事务中，并且
			 * 即将开始一个新命令。目前我们不执行任何操作，但
			 * 将来可能会进行命令局部资源初始化。（注意
			 * 所需的 CommandCounterIncrement 已由
			 * 之前的 CommitTransactionCommand 完成。）
			 */
		case TBLOCK_INPROGRESS:
		case TBLOCK_IMPLICIT_INPROGRESS:
		case TBLOCK_SUBINPROGRESS:
			break;

			/*
			 * 这里我们处于一个失败的事务块中（某个命令
			 * 导致了终止），所以我们只是在终止状态中保持不变。
			 * 最终我们将收到一个 ROLLBACK 命令，将使我们
			 * 脱离这个状态。（其他代码需要确保
			 * 在这些状态中不会处理除 ROLLBACK 之外的其他命令。）
			 */
		case TBLOCK_ABORT:
		case TBLOCK_SUBABORT:
			break;

			/* 这些情况是无效的。 */
		case TBLOCK_STARTED:
		case TBLOCK_BEGIN:
		case TBLOCK_PARALLEL_INPROGRESS:
		case TBLOCK_SUBBEGIN:
		case TBLOCK_END:
		case TBLOCK_SUBRELEASE:
		case TBLOCK_SUBCOMMIT:
		case TBLOCK_ABORT_END:
		case TBLOCK_SUBABORT_END:
		case TBLOCK_ABORT_PENDING:
		case TBLOCK_SUBABORT_PENDING:
		case TBLOCK_SUBRESTART:
		case TBLOCK_SUBABORT_RESTART:
		case TBLOCK_PREPARE:
			elog(ERROR, "StartTransactionCommand: unexpected state %s",
				 fc_BlockStateAsString(fc_s->blockState));
			break;
	}

	/*
	 * 在返回之前必须切换到 CurTransactionContext。这已经在
	 * 调用 StartTransaction 时完成，否则则没有。
	 */
	Assert(CurTransactionContext != NULL);
	MemoryContextSwitchTo(CurTransactionContext);
}


/*
 * 保存和恢复事务特征的简单系统
 * （隔离级别、只读、可延迟）。我们需要这个进行事务
 * 链接，以便我们可以将新事务的特征设置为
 * 与前一个相同。（我们需要这样的东西，因为 GUC
 * 系统在事务结束时重置特征，因此例如
 * 在 StartTransaction() 中跳过重置是行不通的。）
 */
void SaveTransactionCharacteristics(SavedTransactionCharacteristics *fc_s)
{
	fc_s->save_XactIsoLevel = XactIsoLevel;
	fc_s->save_XactReadOnly = XactReadOnly;
	fc_s->save_XactDeferrable = XactDeferrable;
}

void RestoreTransactionCharacteristics(const SavedTransactionCharacteristics *fc_s)
{
	XactIsoLevel = fc_s->save_XactIsoLevel;
	XactReadOnly = fc_s->save_XactReadOnly;
	XactDeferrable = fc_s->save_XactDeferrable;
}


/*
 *	CommitTransactionCommand
 */
void CommitTransactionCommand(void)
{
	TransactionState fc_s = CurrentTransactionState;
	SavedTransactionCharacteristics fc_savetc;

	/* 必须保存，以防我们需要恢复 */
	SaveTransactionCharacteristics(&fc_savetc);

	switch (fc_s->blockState)
	{
			/*
			 * 这些情况不应该发生。TBLOCK_DEFAULT 意味着上一个
			 * StartTransactionCommand 没有正确设置 STARTED 状态，
			 * 而 TBLOCK_PARALLEL_INPROGRESS 应该由 EndParallelWorkerTransaction()
			 * 来结束，而不是这个函数。
			 */
		case TBLOCK_DEFAULT:
		case TBLOCK_PARALLEL_INPROGRESS:
			elog(FATAL, "CommitTransactionCommand: unexpected state %s",
				 fc_BlockStateAsString(fc_s->blockState));
			break;

			/*
			 * 如果我们不在事务块中，只需执行我们通常的
			 * 事务提交，并返回到空闲状态。
			 */
		case TBLOCK_STARTED:
			fc_CommitTransaction();
			fc_s->blockState = TBLOCK_DEFAULT;
			break;

			/*
			 * 我们正在完成一个 "BEGIN TRANSACTION" 命令，因此我们更改
			 * 为 "事务块进行中" 状态并返回。（我们假设 BEGIN 没有对
			 * 数据库进行任何操作，所以不需要
			 * CommandCounterIncrement。）
			 */
		case TBLOCK_BEGIN:
			fc_s->blockState = TBLOCK_INPROGRESS;
			break;

			/*
			 * 这是在我们完成在某个事务块内执行命令的情况。
			 * 我们增加命令计数器并返回。
			 */
		case TBLOCK_INPROGRESS:
		case TBLOCK_IMPLICIT_INPROGRESS:
		case TBLOCK_SUBINPROGRESS:
			CommandCounterIncrement();
			break;

			/*
			 * 我们正在完成一个“COMMIT”命令。执行它并返回到
			 * 空闲状态。
			 */
		case TBLOCK_END:
			fc_CommitTransaction();
			fc_s->blockState = TBLOCK_DEFAULT;
			if (fc_s->chain)
			{
				fc_StartTransaction();
				fc_s->blockState = TBLOCK_INPROGRESS;
				fc_s->chain = false;
				RestoreTransactionCharacteristics(&fc_savetc);
			}
			break;

			/*
			 * 在这里，我们正处于事务块中，但其中一个命令导致了中止，
			 * 所以我们什么也不做，只是保持在中止状态。
			 * 最终我们会收到一个ROLLBACK命令。
			 */
		case TBLOCK_ABORT:
		case TBLOCK_SUBABORT:
			break;

			/*
			 * 在这里，我们处于一个中止的事务块中，我们刚刚从用户那里
			 * 得到了ROLLBACK命令，因此清理已经中止的事务并返回
			 * 空闲状态。
			 */
		case TBLOCK_ABORT_END:
			fc_CleanupTransaction();
			fc_s->blockState = TBLOCK_DEFAULT;
			if (fc_s->chain)
			{
				fc_StartTransaction();
				fc_s->blockState = TBLOCK_INPROGRESS;
				fc_s->chain = false;
				RestoreTransactionCharacteristics(&fc_savetc);
			}
			break;

			/*
			 * 在这里，我们处于一个完全正常的事务块中，但用户
			 * 还是让我们ROLLBACK。我们必须中止事务
			 * 然后进行清理。
			 */
		case TBLOCK_ABORT_PENDING:
			fc_AbortTransaction();
			fc_CleanupTransaction();
			fc_s->blockState = TBLOCK_DEFAULT;
			if (fc_s->chain)
			{
				fc_StartTransaction();
				fc_s->blockState = TBLOCK_INPROGRESS;
				fc_s->chain = false;
				RestoreTransactionCharacteristics(&fc_savetc);
			}
			break;

			/*
			 * 我们正在完成一个“PREPARE TRANSACTION”命令。执行它并
			 * 返回到空闲状态。
			 */
		case TBLOCK_PREPARE:
			fc_PrepareTransaction();
			fc_s->blockState = TBLOCK_DEFAULT;
			break;

			/*
			 * 我们刚刚在事务块内发出了SAVEPOINT命令。
			 * 启动一个子事务。(DefineSavepoint已经执行了
			 * PushTransaction，以便有地方放置SUBBEGIN
			 * 状态。)
			 */
		case TBLOCK_SUBBEGIN:
			fc_StartSubTransaction();
			fc_s->blockState = TBLOCK_SUBINPROGRESS;
			break;

			/*
			 * 我们发出了RELEASE命令，因此我们结束当前
			 * 子事务并返回到父事务。父事务可能也结束，因此
			 * 重复，直到我们找到一个INPROGRESS
			 * 事务或子事务。
			 */
		case TBLOCK_SUBRELEASE:
			do
			{
				fc_CommitSubTransaction();
				fc_s = CurrentTransactionState;	/* changed by pop */
			} while (fc_s->blockState == TBLOCK_SUBRELEASE);

			Assert(fc_s->blockState == TBLOCK_INPROGRESS ||
				   fc_s->blockState == TBLOCK_SUBINPROGRESS);
			break;

			/*
			 * 我们发出了COMMIT命令，因此我们结束当前子事务
			 * 层次结构并执行最终提交。我们通过将
			 * 任何子事务汇总到其父事务中来执行此操作，这会导致
			 * 关于资源拥有者的O(N^2)操作——在接近
			 * 千万个SAVEPOINT时，这并不算太糟，但在后触发器
			 * 创建新资源拥有者的情况下，出于正确性考虑是
			 * 必要的。
			 */
		case TBLOCK_SUBCOMMIT:
			do
			{
				fc_CommitSubTransaction();
				fc_s = CurrentTransactionState;	/* changed by pop */
			} while (fc_s->blockState == TBLOCK_SUBCOMMIT);
			/* 如果我们有一个COMMIT命令，也完成主xact */
			if (fc_s->blockState == TBLOCK_END)
			{
				Assert(fc_s->parent == NULL);
				fc_CommitTransaction();
				fc_s->blockState = TBLOCK_DEFAULT;
				if (fc_s->chain)
				{
					fc_StartTransaction();
					fc_s->blockState = TBLOCK_INPROGRESS;
					fc_s->chain = false;
					RestoreTransactionCharacteristics(&fc_savetc);
				}
			}
			else if (fc_s->blockState == TBLOCK_PREPARE)
			{
				Assert(fc_s->parent == NULL);
				fc_PrepareTransaction();
				fc_s->blockState = TBLOCK_DEFAULT;
			}
			else
				elog(ERROR, "CommitTransactionCommand: unexpected state %s",
					 fc_BlockStateAsString(fc_s->blockState));
			break;

			/*
			 * 当前已经失败的子事务由于ROLLBACK或ROLLBACK TO命令而结束，
			 * 所以弹出它并递归地检查父事务（它可能处于几种状态中的任何一种）。
			 */
		case TBLOCK_SUBABORT_END:
			fc_CleanupSubTransaction();
			CommitTransactionCommand();
			break;

			/*
			 * 如上所述，但它尚未结束，因此先中止。
			 */
		case TBLOCK_SUBABORT_PENDING:
			fc_AbortSubTransaction();
			fc_CleanupSubTransaction();
			CommitTransactionCommand();
			break;

			/*
			 * 当前子事务是ROLLBACK TO命令的目标。
			 * 中止并弹出它，然后以相同名称启动一个新子事务。
			 */
		case TBLOCK_SUBRESTART:
			{
				char	   *fc_name;
				int			fc_savepointLevel;

				/* 保存名称并防止Cleanup释放它 */
				fc_name = fc_s->name;
				fc_s->name = NULL;
				fc_savepointLevel = fc_s->savepointLevel;

				fc_AbortSubTransaction();
				fc_CleanupSubTransaction();

				DefineSavepoint(NULL);
				fc_s = CurrentTransactionState;	/* changed by push */
				fc_s->name = fc_name;
				fc_s->savepointLevel = fc_savepointLevel;

				/* 这与TBLOCK_SUBBEGIN情况相同 */
				AssertState(fc_s->blockState == TBLOCK_SUBBEGIN);
				fc_StartSubTransaction();
				fc_s->blockState = TBLOCK_SUBINPROGRESS;
			}
			break;

			/*
			 * 与上述相同，但子事务已经失败，因此我们
			 * 不需要AbortSubTransaction。
			 */
		case TBLOCK_SUBABORT_RESTART:
			{
				char	   *fc_name;
				int			fc_savepointLevel;

				/* 保存名称并防止Cleanup释放它 */
				fc_name = fc_s->name;
				fc_s->name = NULL;
				fc_savepointLevel = fc_s->savepointLevel;

				fc_CleanupSubTransaction();

				DefineSavepoint(NULL);
				fc_s = CurrentTransactionState;	/* changed by push */
				fc_s->name = fc_name;
				fc_s->savepointLevel = fc_savepointLevel;

				/* 这与TBLOCK_SUBBEGIN情况相同 */
				AssertState(fc_s->blockState == TBLOCK_SUBBEGIN);
				fc_StartSubTransaction();
				fc_s->blockState = TBLOCK_SUBINPROGRESS;
			}
			break;
	}
}

/*
 *	AbortCurrentTransaction
 */
void AbortCurrentTransaction(void)
{
	TransactionState fc_s = CurrentTransactionState;

	switch (fc_s->blockState)
	{
		case TBLOCK_DEFAULT:
			if (fc_s->state == TRANS_DEFAULT)
			{
				/* 我们处于空闲状态，因此无需执行任何操作 */
			}
			else
			{
				/*
				 * 我们在事务开始时出错后可以到达这里
				 * (状态将是 TRANS_START)。需要清理
				 * 未完全开始的事务。首先，调整
				 * 低级状态以抑制来自
				 * AbortTransaction 的警告消息。
				 */
				if (fc_s->state == TRANS_START)
					fc_s->state = TRANS_INPROGRESS;
				fc_AbortTransaction();
				fc_CleanupTransaction();
			}
			break;

			/*
			 * 如果我们不在事务块中，我们只需进行基本的中止
			 * 和清理事务。为此，我们将隐式
			 * 事务块视为简单语句。
			 */
		case TBLOCK_STARTED:
		case TBLOCK_IMPLICIT_INPROGRESS:
			fc_AbortTransaction();
			fc_CleanupTransaction();
			fc_s->blockState = TBLOCK_DEFAULT;
			break;

			/*
			 * 如果我们在 TBLOCK_BEGIN 中，这意味着在读取
			 * “BEGIN TRANSACTION” 后发生了错误。我们假设用户
			 * 会将错误解释为意味着 BEGIN 无法让他
			 * 进入事务块，因此我们应该中止并返回到空闲
			 * 状态。
			 */
		case TBLOCK_BEGIN:
			fc_AbortTransaction();
			fc_CleanupTransaction();
			fc_s->blockState = TBLOCK_DEFAULT;
			break;

			/*
			 * 我们在一个事务块中，并且遇到了
			 * 故障，因此我们中止事务并设置持久的
			 * ABORT 状态。我们将保持在 ABORT 状态，直到我们获得 ROLLBACK。
			 */
		case TBLOCK_INPROGRESS:
		case TBLOCK_PARALLEL_INPROGRESS:
			fc_AbortTransaction();
			fc_s->blockState = TBLOCK_ABORT;
			/* CleanupTransaction 在我们退出 TBLOCK_ABORT_END 时发生 */
			break;

			/*
			 * 在这里，我们尝试 COMMIT 时失败。清理
			 * 事务并返回到空闲状态（我们不想停留在
			 * 事务中）。
			 */
		case TBLOCK_END:
			fc_AbortTransaction();
			fc_CleanupTransaction();
			fc_s->blockState = TBLOCK_DEFAULT;
			break;

			/*
			 * 在这里，我们已经处于中止事务状态，并且
			 * 正在等待 ROLLBACK，但由于某种原因我们再次失败！所以
			 * 我们只是在中止状态中保持不变。
			 */
		case TBLOCK_ABORT:
		case TBLOCK_SUBABORT:
			break;

			/*
			 * 我们在一个失败的事务中，收到了 ROLLBACK 命令。
			 * 我们已经中止，只需清理并转到空闲
			 * 状态。
			 */
		case TBLOCK_ABORT_END:
			fc_CleanupTransaction();
			fc_s->blockState = TBLOCK_DEFAULT;
			break;

			/*
			 * 我们在一个活跃的事务中，收到了 ROLLBACK 命令。
			 * 中止，清理，转到空闲状态。
			 */
		case TBLOCK_ABORT_PENDING:
			fc_AbortTransaction();
			fc_CleanupTransaction();
			fc_s->blockState = TBLOCK_DEFAULT;
			break;

			/*
			 * 在这里，我们尝试 PREPARE 时失败。清理
			 * 事务并返回到空闲状态（我们不想停留在
			 * 事务中）。
			 */
		case TBLOCK_PREPARE:
			fc_AbortTransaction();
			fc_CleanupTransaction();
			fc_s->blockState = TBLOCK_DEFAULT;
			break;

			/*
			 * 我们在一个子事务内遇到错误。仅中止
			 * 子事务，并进入持久的 SUBABORT 状态，直到
			 * 我们获得 ROLLBACK。
			 */
		case TBLOCK_SUBINPROGRESS:
			fc_AbortSubTransaction();
			fc_s->blockState = TBLOCK_SUBABORT;
			break;

			/*
			 * 如果我们在尝试创建子事务时失败，清理
			 * 损坏的子事务并中止父事务。如果我们在结束
			 * 子事务时遇到故障，情况也是如此。
			 */
		case TBLOCK_SUBBEGIN:
		case TBLOCK_SUBRELEASE:
		case TBLOCK_SUBCOMMIT:
		case TBLOCK_SUBABORT_PENDING:
		case TBLOCK_SUBRESTART:
			fc_AbortSubTransaction();
			fc_CleanupSubTransaction();
			AbortCurrentTransaction();
			break;

			/*
			 * 与上述相同，只是 Abort() 已经完成。
			 */
		case TBLOCK_SUBABORT_END:
		case TBLOCK_SUBABORT_RESTART:
			fc_CleanupSubTransaction();
			AbortCurrentTransaction();
			break;
	}
}

/*
 *	PreventInTransactionBlock
 *
 *	这个例程应该被那些不应该在
 *	事务块中运行的语句调用，通常是因为它们有不可回滚的
 *	副作用或进行内部提交。
 *
 *	如果这个例程成功完成，则调用语句保证
 *	如果它没有错误地完成，其结果将被立即提交。
 *
 *	如果我们已经开始了一个事务块，则发出错误；如果我们似乎在
 *	用户定义的函数内运行（这可能会发出更多命令并可能导致
 *	语句完成后失败），也发出错误。子事务也是禁止的。
 *
 *	我们还必须在 MyXactFlags 中设置 XACT_FLAGS_NEEDIMMEDIATECOMMIT，以确保
 *postgres.c 在语句完成后进行提交。
 *
 *	isTopLevel: 从 ProcessUtility 传递下来，用于确定我们是否在
 *	函数内部。（如果为 false，我们将始终失败，但在此集中检查
 *	是方便的，而不是让调用者来做。）
 *	stmtType: 语句类型名称，用于错误消息。
 */
void PreventInTransactionBlock(bool fc_isTopLevel, const char *fc_stmtType)
{
	/*
	 * xact block 已经开始了吗？
	 */
	if (IsTransactionBlock())
		ereport(ERROR,
				(errcode(ERRCODE_ACTIVE_SQL_TRANSACTION),
		/* translator: %s 表示一个 SQL 语句名称 */
				 errmsg("%s cannot run inside a transaction block",
						fc_stmtType)));

	/*
	 * 子事务？
	 */
	if (IsSubTransaction())
		ereport(ERROR,
				(errcode(ERRCODE_ACTIVE_SQL_TRANSACTION),
		/* translator: %s 表示一个 SQL 语句名称 */
				 errmsg("%s cannot run inside a subtransaction",
						fc_stmtType)));

	/*
	 * 在启动隐式事务的管道内？
	 */
	if (MyXactFlags & XACT_FLAGS_PIPELINING)
		ereport(ERROR,
				(errcode(ERRCODE_ACTIVE_SQL_TRANSACTION),
		/* translator: %s 表示一个 SQL 语句名称 */
				 errmsg("%s cannot be executed within a pipeline",
						fc_stmtType)));

	/*
	 * 在函数调用内？
	 */
	if (!fc_isTopLevel)
		ereport(ERROR,
				(errcode(ERRCODE_ACTIVE_SQL_TRANSACTION),
		/* translator: %s 表示一个 SQL 语句名称 */
				 errmsg("%s cannot be executed from a function", fc_stmtType)));

	/* 如果我们通过了 IsTransactionBlock 测试，应处于默认状态 */
	if (CurrentTransactionState->blockState != TBLOCK_DEFAULT &&
		CurrentTransactionState->blockState != TBLOCK_STARTED)
		elog(FATAL, "cannot prevent transaction chain");

	/* 一切正常。设置标志以确保稍后发生正确的事情。 */
	MyXactFlags |= XACT_FLAGS_NEEDIMMEDIATECOMMIT;
}

/*
 *	WarnNoTransactionBlock
 *	RequireTransactionBlock
 *
 *	这两个函数允许在命令在事务块外执行时发出警告或错误。
 *	这对于那些在事务结束后没有持久效果的命令很有用
 *	（因此在事务块外调用它们可以认为是错误）。DECLARE CURSOR 就是一个例子。
 *	虽然顶层事务控制命令（BEGIN/COMMIT/ABORT）和 SET
 *	没有效果时发出警告，但所有其他无效命令都会生成
 *	错误。
 *
 *	如果我们似乎在用户定义的函数内部运行，则不发出任何内容，
 *	因为函数可能会发出更多命令以利用当前
 *	语句的结果。同样，子事务也是如此。
 *	因此，这些是 PreventInTransactionBlock 的逆。
 *
 *	isTopLevel: 从 ProcessUtility 传递下来的，用于确定我们是否在
 *	函数内部。
 *	stmtType: 语句类型名称，用于警告或错误消息。
 */
void WarnNoTransactionBlock(bool fc_isTopLevel, const char *fc_stmtType)
{
	fc_CheckTransactionBlock(fc_isTopLevel, false, fc_stmtType);
}

void RequireTransactionBlock(bool fc_isTopLevel, const char *fc_stmtType)
{
	fc_CheckTransactionBlock(fc_isTopLevel, true, fc_stmtType);
}

/*
 * 这是上述两个的实现。
 */
static void fc_CheckTransactionBlock(bool fc_isTopLevel, bool fc_throwError, const char *fc_stmtType)
{
	/*
	 * xact block 已经开始了吗？
	 */
	if (IsTransactionBlock())
		return;

	/*
	 * 子事务？
	 */
	if (IsSubTransaction())
		return;

	/*
	 * 在函数调用内？
	 */
	if (!fc_isTopLevel)
		return;

	ereport(fc_throwError ? ERROR : WARNING,
			(errcode(ERRCODE_NO_ACTIVE_SQL_TRANSACTION),
	/* translator: %s 表示一个 SQL 语句名称 */
			 errmsg("%s can only be used in transaction blocks",
					fc_stmtType)));
}

/*
 *	IsInTransactionBlock
 *
 *	这个例程用于在事务块内需要表现得与作为单独命令运行时不同的语句。ANALYZE 目前是唯一的例子。
 *
 *	如果该例程返回“false”，则调用语句被允许执行内部的事务提交和启动周期；不会有损害任何正在进行的事务的风险。（注意，这与 PreventInTransactionBlock 提供的保证并不完全相同，因为我们不会强制进行语句后的提交。）
 *
 *	isTopLevel: 从 ProcessUtility 传递下来以确定我们是否在函数内。
 */
bool IsInTransactionBlock(bool fc_isTopLevel)
{
	/*
	 * 在与 PreventInTransactionBlock 报错相同的条件下返回 true
	 */
	if (IsTransactionBlock())
		return true;

	if (IsSubTransaction())
		return true;

	if (MyXactFlags & XACT_FLAGS_PIPELINING)
		return true;

	if (!fc_isTopLevel)
		return true;

	if (CurrentTransactionState->blockState != TBLOCK_DEFAULT &&
		CurrentTransactionState->blockState != TBLOCK_STARTED)
		return true;

	return false;
}


/*
 * 注册或注销用于开始和结束 xact 操作的回调函数。
 *
 * 这些函数旨在供动态加载的模块使用。对于内置模块，我们通常直接硬编码适当的调用（主要是因为这样更容易控制顺序，在需要时）。
 *
 * 在事务结束时，回调在提交后或中止后发生，因此回调函数只能进行非关键性的清理。
 */
void RegisterXactCallback(XactCallback fc_callback, void *fc_arg)
{
	XactCallbackItem *fc_item;

	fc_item = (XactCallbackItem *)
		MemoryContextAlloc(TopMemoryContext, sizeof(XactCallbackItem));
	fc_item->callback = fc_callback;
	fc_item->arg = fc_arg;
	fc_item->next = Xact_callbacks;
	Xact_callbacks = fc_item;
}

void UnregisterXactCallback(XactCallback fc_callback, void *fc_arg)
{
	XactCallbackItem *fc_item;
	XactCallbackItem *fc_prev;

	fc_prev = NULL;
	for (fc_item = Xact_callbacks; fc_item; fc_prev = fc_item, fc_item = fc_item->next)
	{
		if (fc_item->callback == fc_callback && fc_item->arg == fc_arg)
		{
			if (fc_prev)
				fc_prev->next = fc_item->next;
			else
				Xact_callbacks = fc_item->next;
			pfree(fc_item);
			break;
		}
	}
}

static void fc_CallXactCallbacks(XactEvent fc_event)
{
	XactCallbackItem *fc_item;

	for (fc_item = Xact_callbacks; fc_item; fc_item = fc_item->next)
		fc_item->callback(fc_event, fc_item->arg);
}


/*
 * 注册或注销用于开始和结束 subxact 操作的回调函数。
 *
 * 与上述内容基本相同，但用于子事务事件。
 *
 * 在子事务结束时，回调在子提交后或子中止后发生，因此回调函数只能进行非关键性的清理。在子事务开始时，回调在子事务初始化完成时被调用。
 */
void RegisterSubXactCallback(SubXactCallback fc_callback, void *fc_arg)
{
	SubXactCallbackItem *fc_item;

	fc_item = (SubXactCallbackItem *)
		MemoryContextAlloc(TopMemoryContext, sizeof(SubXactCallbackItem));
	fc_item->callback = fc_callback;
	fc_item->arg = fc_arg;
	fc_item->next = SubXact_callbacks;
	SubXact_callbacks = fc_item;
}

void UnregisterSubXactCallback(SubXactCallback fc_callback, void *fc_arg)
{
	SubXactCallbackItem *fc_item;
	SubXactCallbackItem *fc_prev;

	fc_prev = NULL;
	for (fc_item = SubXact_callbacks; fc_item; fc_prev = fc_item, fc_item = fc_item->next)
	{
		if (fc_item->callback == fc_callback && fc_item->arg == fc_arg)
		{
			if (fc_prev)
				fc_prev->next = fc_item->next;
			else
				SubXact_callbacks = fc_item->next;
			pfree(fc_item);
			break;
		}
	}
}

static void fc_CallSubXactCallbacks(SubXactEvent fc_event,
					 SubTransactionId fc_mySubid,
					 SubTransactionId fc_parentSubid)
{
	SubXactCallbackItem *fc_item;

	for (fc_item = SubXact_callbacks; fc_item; fc_item = fc_item->next)
		fc_item->callback(fc_event, fc_mySubid, fc_parentSubid, fc_item->arg);
}


/* ----------------------------------------------------------------
 *					   事务块支持
 * ----------------------------------------------------------------
 */

/*
 *	BeginTransactionBlock
 *		这执行一个 BEGIN 命令。
 */
void BeginTransactionBlock(void)
{
	TransactionState fc_s = CurrentTransactionState;

	switch (fc_s->blockState)
	{
			/*
			 * 我们不在一个事务块内，因此允许开始一个。
			 */
		case TBLOCK_STARTED:
			fc_s->blockState = TBLOCK_BEGIN;
			break;

			/*
			 * BEGIN 将隐式事务块转换为常规事务块。
			 * （注意，即使我们已经执行了一些命令，我们也允许这样，这是有点奇怪但符合历史惯例。）
			 */
		case TBLOCK_IMPLICIT_INPROGRESS:
			fc_s->blockState = TBLOCK_BEGIN;
			break;

			/*
			 * 已经有一个事务块在进行中。
			 */
		case TBLOCK_INPROGRESS:
		case TBLOCK_PARALLEL_INPROGRESS:
		case TBLOCK_SUBINPROGRESS:
		case TBLOCK_ABORT:
		case TBLOCK_SUBABORT:
			ereport(WARNING,
					(errcode(ERRCODE_ACTIVE_SQL_TRANSACTION),
					 errmsg("there is already a transaction in progress")));
			break;

			/* 这些情况是无效的。 */
		case TBLOCK_DEFAULT:
		case TBLOCK_BEGIN:
		case TBLOCK_SUBBEGIN:
		case TBLOCK_END:
		case TBLOCK_SUBRELEASE:
		case TBLOCK_SUBCOMMIT:
		case TBLOCK_ABORT_END:
		case TBLOCK_SUBABORT_END:
		case TBLOCK_ABORT_PENDING:
		case TBLOCK_SUBABORT_PENDING:
		case TBLOCK_SUBRESTART:
		case TBLOCK_SUBABORT_RESTART:
		case TBLOCK_PREPARE:
			elog(FATAL, "BeginTransactionBlock: unexpected state %s",
				 fc_BlockStateAsString(fc_s->blockState));
			break;
	}
}

/*
 *	PrepareTransactionBlock
 *		这执行一个 PREPARE 命令。
 *
 * 由于 PREPARE 可能实际上会执行 ROLLBACK，因此结果指示发生了什么：对于 PREPARE 为 true，对于 ROLLBACK 为 false。
 *
 * 注意，我们在这里实际上什么也不做，只是改变 blockState。
 * 实际工作将在即将到来的 PrepareTransaction() 中完成。
 * 我们这样做是因为在执行 Portal 内部时，更改内存上下文、资源拥有者等并不方便。
 */
bool PrepareTransactionBlock(const char *fc_gid)
{
	TransactionState fc_s;
	bool		fc_result;

	/* 设置以提交当前事务 */
	fc_result = EndTransactionBlock(false);

	/* 如果成功，则将外部 tblock 状态更改为 PREPARE */
	if (fc_result)
	{
		fc_s = CurrentTransactionState;

		while (fc_s->parent != NULL)
			fc_s = fc_s->parent;

		if (fc_s->blockState == TBLOCK_END)
		{
			/* 保存 GID，以便 PrepareTransaction 可以再次找到它 */
			prepareGID = MemoryContextStrdup(TopTransactionContext, fc_gid);

			fc_s->blockState = TBLOCK_PREPARE;
		}
		else
		{
			/*
			 * 忽略不在事务中的情况；
			 * EndTransactionBlock 已经发出了一条警告。
			 */
			Assert(fc_s->blockState == TBLOCK_STARTED ||
				   fc_s->blockState == TBLOCK_IMPLICIT_INPROGRESS);
			/* 不要发送 PREPARE 结果标签... */
			fc_result = false;
		}
	}

	return fc_result;
}

/*
 *	EndTransactionBlock
 *		这将执行 COMMIT 命令。
 *
 * 由于 COMMIT 实际上可能会执行 ROLLBACK，因此结果指示发生了什么：
 * true 表示 COMMIT，false 表示 ROLLBACK。
 *
 * 请注意，我们在这里实际上不执行任何操作，只是改变 blockState。
 * 真正的工作将在接下来的 CommitTransactionCommand() 中完成。
 * 我们这样做是因为在 Portal 内部执行时改变内存上下文、
 * 资源所有者等并不方便。
 */
bool EndTransactionBlock(bool fc_chain)
{
	TransactionState fc_s = CurrentTransactionState;
	bool		fc_result = false;

	switch (fc_s->blockState)
	{
			/*
			 * 我们在一个事务块中，所以告诉 CommitTransactionCommand
			 * 去 COMMIT。
			 */
		case TBLOCK_INPROGRESS:
			fc_s->blockState = TBLOCK_END;
			fc_result = true;
			break;

			/*
			 * 我们在一个隐式事务块中。如果指定了 AND CHAIN，则错误。
			 * 否则提交，但发出警告，因为之前没有显式 BEGIN。
			 */
		case TBLOCK_IMPLICIT_INPROGRESS:
			if (fc_chain)
				ereport(ERROR,
						(errcode(ERRCODE_NO_ACTIVE_SQL_TRANSACTION),
				/* translator: %s 表示一个 SQL 语句名称 */
						 errmsg("%s can only be used in transaction blocks",
								"COMMIT AND CHAIN")));
			else
				ereport(WARNING,
						(errcode(ERRCODE_NO_ACTIVE_SQL_TRANSACTION),
						 errmsg("there is no transaction in progress")));
			fc_s->blockState = TBLOCK_END;
			fc_result = true;
			break;

			/*
			 * 我们在一个失败的事务块中。告诉
			 * CommitTransactionCommand 现在是退出块的时候。
			 */
		case TBLOCK_ABORT:
			fc_s->blockState = TBLOCK_ABORT_END;
			break;

			/*
			 * 我们在一个活动的子事务块中。准备对子事务进行提交，然后提交主事务。
			 */
		case TBLOCK_SUBINPROGRESS:
			while (fc_s->parent != NULL)
			{
				if (fc_s->blockState == TBLOCK_SUBINPROGRESS)
					fc_s->blockState = TBLOCK_SUBCOMMIT;
				else
					elog(FATAL, "EndTransactionBlock: unexpected state %s",
						 fc_BlockStateAsString(fc_s->blockState));
				fc_s = fc_s->parent;
			}
			if (fc_s->blockState == TBLOCK_INPROGRESS)
				fc_s->blockState = TBLOCK_END;
			else
				elog(FATAL, "EndTransactionBlock: unexpected state %s",
					 fc_BlockStateAsString(fc_s->blockState));
			fc_result = true;
			break;

			/*
			 * 在这里我们处于一个中止的子事务中。将 COMMIT 视为 ROLLBACK：准备中止一切并退出主事务。
			 */
		case TBLOCK_SUBABORT:
			while (fc_s->parent != NULL)
			{
				if (fc_s->blockState == TBLOCK_SUBINPROGRESS)
					fc_s->blockState = TBLOCK_SUBABORT_PENDING;
				else if (fc_s->blockState == TBLOCK_SUBABORT)
					fc_s->blockState = TBLOCK_SUBABORT_END;
				else
					elog(FATAL, "EndTransactionBlock: unexpected state %s",
						 fc_BlockStateAsString(fc_s->blockState));
				fc_s = fc_s->parent;
			}
			if (fc_s->blockState == TBLOCK_INPROGRESS)
				fc_s->blockState = TBLOCK_ABORT_PENDING;
			else if (fc_s->blockState == TBLOCK_ABORT)
				fc_s->blockState = TBLOCK_ABORT_END;
			else
				elog(FATAL, "EndTransactionBlock: unexpected state %s",
					 fc_BlockStateAsString(fc_s->blockState));
			break;

			/*
			 * 用户在不在事务中时发出了 COMMIT。对于
			 * 没有 CHAIN 的 COMMIT，发出警告，保持在
			 * TBLOCK_STARTED 状态。即将调用
			 * CommitTransactionCommand() 将关闭事务并将我们带回默认状态。对于 COMMIT AND CHAIN，
			 * 错误。
			 */
		case TBLOCK_STARTED:
			if (fc_chain)
				ereport(ERROR,
						(errcode(ERRCODE_NO_ACTIVE_SQL_TRANSACTION),
				/* translator: %s 表示一个 SQL 语句名称 */
						 errmsg("%s can only be used in transaction blocks",
								"COMMIT AND CHAIN")));
			else
				ereport(WARNING,
						(errcode(ERRCODE_NO_ACTIVE_SQL_TRANSACTION),
						 errmsg("there is no transaction in progress")));
			fc_result = true;
			break;

			/*
			 * 用户发出了一个 COMMIT，但它在一个并行
			 * 工作者中运行。我们无法处理。
			 */
		case TBLOCK_PARALLEL_INPROGRESS:
			ereport(FATAL,
					(errcode(ERRCODE_INVALID_TRANSACTION_STATE),
					 errmsg("cannot commit during a parallel operation")));
			break;

			/* 这些情况是无效的。 */
		case TBLOCK_DEFAULT:
		case TBLOCK_BEGIN:
		case TBLOCK_SUBBEGIN:
		case TBLOCK_END:
		case TBLOCK_SUBRELEASE:
		case TBLOCK_SUBCOMMIT:
		case TBLOCK_ABORT_END:
		case TBLOCK_SUBABORT_END:
		case TBLOCK_ABORT_PENDING:
		case TBLOCK_SUBABORT_PENDING:
		case TBLOCK_SUBRESTART:
		case TBLOCK_SUBABORT_RESTART:
		case TBLOCK_PREPARE:
			elog(FATAL, "EndTransactionBlock: unexpected state %s",
				 fc_BlockStateAsString(fc_s->blockState));
			break;
	}

	Assert(fc_s->blockState == TBLOCK_STARTED ||
		   fc_s->blockState == TBLOCK_END ||
		   fc_s->blockState == TBLOCK_ABORT_END ||
		   fc_s->blockState == TBLOCK_ABORT_PENDING);

	fc_s->chain = fc_chain;

	return fc_result;
}

/*
 *	UserAbortTransactionBlock
 *		这将执行 ROLLBACK 命令。
 *
 * 和上面一样，我们在这里实际上不执行任何操作，只是改变 blockState。
 */
void UserAbortTransactionBlock(bool fc_chain)
{
	TransactionState fc_s = CurrentTransactionState;

	switch (fc_s->blockState)
	{
			/*
			 * 我们在一个事务块中，并且得到了用户的 ROLLBACK 命令，
			 * 所以告诉 CommitTransactionCommand 进行中止并
			 * 退出事务块。
			 */
		case TBLOCK_INPROGRESS:
			fc_s->blockState = TBLOCK_ABORT_PENDING;
			break;

			/*
			 * 我们在一个失败的事务块中，并且得到了用户的 ROLLBACK
			 * 命令。中止处理已经完成，因此
			 * CommitTransactionCommand 只需清理并返回
			 * 空闲状态。
			 */
		case TBLOCK_ABORT:
			fc_s->blockState = TBLOCK_ABORT_END;
			break;

			/*
			 * 我们在一个子事务中。将所有内容标记为可退出，直到顶层。
			 */
		case TBLOCK_SUBINPROGRESS:
		case TBLOCK_SUBABORT:
			while (fc_s->parent != NULL)
			{
				if (fc_s->blockState == TBLOCK_SUBINPROGRESS)
					fc_s->blockState = TBLOCK_SUBABORT_PENDING;
				else if (fc_s->blockState == TBLOCK_SUBABORT)
					fc_s->blockState = TBLOCK_SUBABORT_END;
				else
					elog(FATAL, "UserAbortTransactionBlock: unexpected state %s",
						 fc_BlockStateAsString(fc_s->blockState));
				fc_s = fc_s->parent;
			}
			if (fc_s->blockState == TBLOCK_INPROGRESS)
				fc_s->blockState = TBLOCK_ABORT_PENDING;
			else if (fc_s->blockState == TBLOCK_ABORT)
				fc_s->blockState = TBLOCK_ABORT_END;
			else
				elog(FATAL, "UserAbortTransactionBlock: unexpected state %s",
					 fc_BlockStateAsString(fc_s->blockState));
			break;

			/*
			 * 用户在未处于事务中时发出了 ABORT。对于没有 CHAIN 的 ROLLBACK，发出警告并进入中止状态。
			 * 接下来的 CommitTransactionCommand() 调用将使我们恢复到默认状态。对于 ROLLBACK AND CHAIN，错误。
			 *
			 * 我们在隐式事务中对 ABORT 做同样的处理，尽管在这种情况下我们可能正在回滚实际的数据库状态更改。
			 * （在这种情况下是否应该发出警告是有争议的，但我们历史上一直这样做。）
			 */
		case TBLOCK_STARTED:
		case TBLOCK_IMPLICIT_INPROGRESS:
			if (fc_chain)
				ereport(ERROR,
						(errcode(ERRCODE_NO_ACTIVE_SQL_TRANSACTION),
				/* translator: %s 表示一个 SQL 语句名称 */
						 errmsg("%s can only be used in transaction blocks",
								"ROLLBACK AND CHAIN")));
			else
				ereport(WARNING,
						(errcode(ERRCODE_NO_ACTIVE_SQL_TRANSACTION),
						 errmsg("there is no transaction in progress")));
			fc_s->blockState = TBLOCK_ABORT_PENDING;
			break;

			/*
			 * 用户发出了一个在并行工作者中运行的 ABORT。我们无法处理这种情况。
			 */
		case TBLOCK_PARALLEL_INPROGRESS:
			ereport(FATAL,
					(errcode(ERRCODE_INVALID_TRANSACTION_STATE),
					 errmsg("cannot abort during a parallel operation")));
			break;

			/* 这些情况是无效的。 */
		case TBLOCK_DEFAULT:
		case TBLOCK_BEGIN:
		case TBLOCK_SUBBEGIN:
		case TBLOCK_END:
		case TBLOCK_SUBRELEASE:
		case TBLOCK_SUBCOMMIT:
		case TBLOCK_ABORT_END:
		case TBLOCK_SUBABORT_END:
		case TBLOCK_ABORT_PENDING:
		case TBLOCK_SUBABORT_PENDING:
		case TBLOCK_SUBRESTART:
		case TBLOCK_SUBABORT_RESTART:
		case TBLOCK_PREPARE:
			elog(FATAL, "UserAbortTransactionBlock: unexpected state %s",
				 fc_BlockStateAsString(fc_s->blockState));
			break;
	}

	Assert(fc_s->blockState == TBLOCK_ABORT_END ||
		   fc_s->blockState == TBLOCK_ABORT_PENDING);

	fc_s->chain = fc_chain;
}

/*
 * BeginImplicitTransactionBlock
 *		如果我们还不在隐式事务块中，则开始一个隐式事务块。
 *
 * 与 BeginTransactionBlock 不同，这直接从 postgres.c 的主循环中调用，
 * 而不是在 Portal 中。因此我们可以在没有太多程序的情况下直接更改 blockState。
 * 我们不期望调用者执行 CommitTransactionCommand/StartTransactionCommand。
 */
void BeginImplicitTransactionBlock(void)
{
	TransactionState fc_s = CurrentTransactionState;

	/*
	 * 如果我们处于 STARTED 状态（即没有打开事务块），
	 * 切换到 IMPLICIT_INPROGRESS 状态，创建一个隐式事务块。
	 *
	 * 为了方便调用者，我们在这里将所有其他事务状态视为合法；
	 * 否则，调用者将需要自己的状态检查，这似乎相当没有意义。
	 */
	if (fc_s->blockState == TBLOCK_STARTED)
		fc_s->blockState = TBLOCK_IMPLICIT_INPROGRESS;
}

/*
 * EndImplicitTransactionBlock
 *		结束一个隐式事务块，如果我们正处于其中。
 *
 * 与 EndTransactionBlock 类似，我们在这里仅进行任何需要的 blockState 更改。
 * 实际工作将在即将到来的 CommitTransactionCommand() 中完成。
 */
void EndImplicitTransactionBlock(void)
{
	TransactionState fc_s = CurrentTransactionState;

	/*
	 * 如果我们处于 IMPLICIT_INPROGRESS 状态，切换回 STARTED 状态，
	 * 允许 CommitTransactionCommand 提交隐式事务块期间发生的任何操作，就像它是单个语句一样。
	 *
	 * 为了方便调用者，我们在这里将所有其他事务状态视为合法；
	 * 否则，调用者将需要自己的状态检查，这似乎相当没有意义。
	 */
	if (fc_s->blockState == TBLOCK_IMPLICIT_INPROGRESS)
		fc_s->blockState = TBLOCK_STARTED;
}

/*
 * DefineSavepoint
 *		这执行一个 SAVEPOINT 命令。
 */
void DefineSavepoint(const char *fc_name)
{
	TransactionState fc_s = CurrentTransactionState;

	/*
	 * 工作者在每个并行操作开始时同步事务状态，因此我们无法在那之后考虑新的子事务。
	 * （请注意，如果 s->blockState 是 TBLOCK_PARALLEL_INPROGRESS，这个检查肯定会出错，
	 * 因此我们可以将其视为下面的无效案例。）
	 */
	if (IsInParallelMode())
		ereport(ERROR,
				(errcode(ERRCODE_INVALID_TRANSACTION_STATE),
				 errmsg("cannot define savepoints during a parallel operation")));

	switch (fc_s->blockState)
	{
		case TBLOCK_INPROGRESS:
		case TBLOCK_SUBINPROGRESS:
			/* 正常的子事务开始 */
			fc_PushTransaction();
			fc_s = CurrentTransactionState;	/* changed by push */

			/*
			 * 保存点名称，如同 TransactionState 块本身，存在于 TopTransactionContext 中。
			 */
			if (fc_name)
				fc_s->name = MemoryContextStrdup(TopTransactionContext, fc_name);
			break;

			/*
			 * 我们不允许在隐式事务块中使用保存点命令。
			 * 就本模块而言，允许它们不会造成太大困难，但保存点似乎与
			 * exec_simple_query在出错时放弃整个查询字符串的行为不一致。
			 * 此外，隐式事务块的目的是在出错后自动关闭，因此很难看出
			 * 保存点如何适应这一点。
			 *
			 * 针对此的错误消息表述得就像根本没有活动事务块一样，
			 * 这虽然是历史遗留下来的，但或许可以改进。
			 */
		case TBLOCK_IMPLICIT_INPROGRESS:
			ereport(ERROR,
					(errcode(ERRCODE_NO_ACTIVE_SQL_TRANSACTION),
			/* translator: %s 表示一个 SQL 语句名称 */
					 errmsg("%s can only be used in transaction blocks",
							"SAVEPOINT")));
			break;

			/* 这些情况是无效的。 */
		case TBLOCK_DEFAULT:
		case TBLOCK_STARTED:
		case TBLOCK_BEGIN:
		case TBLOCK_PARALLEL_INPROGRESS:
		case TBLOCK_SUBBEGIN:
		case TBLOCK_END:
		case TBLOCK_SUBRELEASE:
		case TBLOCK_SUBCOMMIT:
		case TBLOCK_ABORT:
		case TBLOCK_SUBABORT:
		case TBLOCK_ABORT_END:
		case TBLOCK_SUBABORT_END:
		case TBLOCK_ABORT_PENDING:
		case TBLOCK_SUBABORT_PENDING:
		case TBLOCK_SUBRESTART:
		case TBLOCK_SUBABORT_RESTART:
		case TBLOCK_PREPARE:
			elog(FATAL, "DefineSavepoint: unexpected state %s",
				 fc_BlockStateAsString(fc_s->blockState));
			break;
	}
}

/*
 * 释放保存点
 *		这会执行一个 RELEASE 命令。
 *
 * 正如上文所述，我们在这里实际上不做任何事情，只是改变 blockState。
 */
void ReleaseSavepoint(const char *fc_name)
{
	TransactionState fc_s = CurrentTransactionState;
	TransactionState fc_target,
				fc_xact;

	/*
	 * 工作线程在每个并行操作开始时同步事务状态，因此我们无法
	 * 在那之后考虑事务状态的变化。（请注意，如果 s->blockState
	 * 是 TBLOCK_PARALLEL_INPROGRESS，此检查肯定会出错，所以我们
	 * 可以把它视为下面的无效情况。）
	 */
	if (IsInParallelMode())
		ereport(ERROR,
				(errcode(ERRCODE_INVALID_TRANSACTION_STATE),
				 errmsg("cannot release savepoints during a parallel operation")));

	switch (fc_s->blockState)
	{
			/*
			 * 如果没有定义保存点，我们无法释放保存点。
			 */
		case TBLOCK_INPROGRESS:
			ereport(ERROR,
					(errcode(ERRCODE_S_E_INVALID_SPECIFICATION),
					 errmsg("savepoint \"%s\" does not exist", fc_name)));
			break;

		case TBLOCK_IMPLICIT_INPROGRESS:
			/* 请参阅关于 DefineSavepoint 中隐式事务的评论 */
			ereport(ERROR,
					(errcode(ERRCODE_NO_ACTIVE_SQL_TRANSACTION),
			/* translator: %s 表示一个 SQL 语句名称 */
					 errmsg("%s can only be used in transaction blocks",
							"RELEASE SAVEPOINT")));
			break;

			/*
			 * 我们处于一个未中止的子事务中。这是唯一有效的情况。
			 */
		case TBLOCK_SUBINPROGRESS:
			break;

			/* 这些情况是无效的。 */
		case TBLOCK_DEFAULT:
		case TBLOCK_STARTED:
		case TBLOCK_BEGIN:
		case TBLOCK_PARALLEL_INPROGRESS:
		case TBLOCK_SUBBEGIN:
		case TBLOCK_END:
		case TBLOCK_SUBRELEASE:
		case TBLOCK_SUBCOMMIT:
		case TBLOCK_ABORT:
		case TBLOCK_SUBABORT:
		case TBLOCK_ABORT_END:
		case TBLOCK_SUBABORT_END:
		case TBLOCK_ABORT_PENDING:
		case TBLOCK_SUBABORT_PENDING:
		case TBLOCK_SUBRESTART:
		case TBLOCK_SUBABORT_RESTART:
		case TBLOCK_PREPARE:
			elog(FATAL, "ReleaseSavepoint: unexpected state %s",
				 fc_BlockStateAsString(fc_s->blockState));
			break;
	}

	for (fc_target = fc_s; PointerIsValid(fc_target); fc_target = fc_target->parent)
	{
		if (PointerIsValid(fc_target->name) && strcmp(fc_target->name, fc_name) == 0)
			break;
	}

	if (!PointerIsValid(fc_target))
		ereport(ERROR,
				(errcode(ERRCODE_S_E_INVALID_SPECIFICATION),
				 errmsg("savepoint \"%s\" does not exist", fc_name)));

	/* 不允许跨越保存点级别边界 */
	if (fc_target->savepointLevel != fc_s->savepointLevel)
		ereport(ERROR,
				(errcode(ERRCODE_S_E_INVALID_SPECIFICATION),
				 errmsg("savepoint \"%s\" does not exist within current savepoint level", fc_name)));

	/*
	 * 将所有子事务标记为“提交待定”，直到目标
	 * 子事务。实际的提交将在控制权到达
	 * CommitTransactionCommand 时发生。
	 */
	fc_xact = CurrentTransactionState;
	for (;;)
	{
		Assert(fc_xact->blockState == TBLOCK_SUBINPROGRESS);
		fc_xact->blockState = TBLOCK_SUBRELEASE;
		if (fc_xact == fc_target)
			break;
		fc_xact = fc_xact->parent;
		Assert(PointerIsValid(fc_xact));
	}
}

/*
 * 回滚到保存点
 *		这会执行一个 ROLLBACK TO <savepoint> 命令。
 *
 * 正如上文所述，我们在这里实际上不做任何事情，只是改变 blockState。
 */
void RollbackToSavepoint(const char *fc_name)
{
	TransactionState fc_s = CurrentTransactionState;
	TransactionState fc_target,
				fc_xact;

	/*
	 * 工作线程在每个并行操作开始时同步事务状态，因此我们无法
	 * 在那之后考虑事务状态的变化。（请注意，如果 s->blockState
	 * 是 TBLOCK_PARALLEL_INPROGRESS，此检查肯定会出错，所以我们
	 * 可以把它视为下面的无效情况。）
	 */
	if (IsInParallelMode())
		ereport(ERROR,
				(errcode(ERRCODE_INVALID_TRANSACTION_STATE),
				 errmsg("cannot rollback to savepoints during a parallel operation")));

	switch (fc_s->blockState)
	{
			/*
			 * 如果没有定义保存点，我们无法回滚到保存点。
			 */
		case TBLOCK_INPROGRESS:
		case TBLOCK_ABORT:
			ereport(ERROR,
					(errcode(ERRCODE_S_E_INVALID_SPECIFICATION),
					 errmsg("savepoint \"%s\" does not exist", fc_name)));
			break;

		case TBLOCK_IMPLICIT_INPROGRESS:
			/* 请参阅关于 DefineSavepoint 中隐式事务的评论 */
			ereport(ERROR,
					(errcode(ERRCODE_NO_ACTIVE_SQL_TRANSACTION),
			/* translator: %s 表示一个 SQL 语句名称 */
					 errmsg("%s can only be used in transaction blocks",
							"ROLLBACK TO SAVEPOINT")));
			break;

			/*
			 * 至少有一个保存点，所以可以继续。
			 */
		case TBLOCK_SUBINPROGRESS:
		case TBLOCK_SUBABORT:
			break;

			/* 这些情况是无效的。 */
		case TBLOCK_DEFAULT:
		case TBLOCK_STARTED:
		case TBLOCK_BEGIN:
		case TBLOCK_PARALLEL_INPROGRESS:
		case TBLOCK_SUBBEGIN:
		case TBLOCK_END:
		case TBLOCK_SUBRELEASE:
		case TBLOCK_SUBCOMMIT:
		case TBLOCK_ABORT_END:
		case TBLOCK_SUBABORT_END:
		case TBLOCK_ABORT_PENDING:
		case TBLOCK_SUBABORT_PENDING:
		case TBLOCK_SUBRESTART:
		case TBLOCK_SUBABORT_RESTART:
		case TBLOCK_PREPARE:
			elog(FATAL, "RollbackToSavepoint: unexpected state %s",
				 fc_BlockStateAsString(fc_s->blockState));
			break;
	}

	for (fc_target = fc_s; PointerIsValid(fc_target); fc_target = fc_target->parent)
	{
		if (PointerIsValid(fc_target->name) && strcmp(fc_target->name, fc_name) == 0)
			break;
	}

	if (!PointerIsValid(fc_target))
		ereport(ERROR,
				(errcode(ERRCODE_S_E_INVALID_SPECIFICATION),
				 errmsg("savepoint \"%s\" does not exist", fc_name)));

	/* 不允许跨越保存点级别边界 */
	if (fc_target->savepointLevel != fc_s->savepointLevel)
		ereport(ERROR,
				(errcode(ERRCODE_S_E_INVALID_SPECIFICATION),
				 errmsg("savepoint \"%s\" does not exist within current savepoint level", fc_name)));

	/*
	 * 将所有子事务标记为“中止待定”，直到目标
	 * 子事务。实际的中止将在控制权到达
	 * CommitTransactionCommand 时发生。
	 */
	fc_xact = CurrentTransactionState;
	for (;;)
	{
		if (fc_xact == fc_target)
			break;
		if (fc_xact->blockState == TBLOCK_SUBINPROGRESS)
			fc_xact->blockState = TBLOCK_SUBABORT_PENDING;
		else if (fc_xact->blockState == TBLOCK_SUBABORT)
			fc_xact->blockState = TBLOCK_SUBABORT_END;
		else
			elog(FATAL, "RollbackToSavepoint: unexpected state %s",
				 fc_BlockStateAsString(fc_xact->blockState));
		fc_xact = fc_xact->parent;
		Assert(PointerIsValid(fc_xact));
	}

	/* 并将目标标记为“重启待定” */
	if (fc_xact->blockState == TBLOCK_SUBINPROGRESS)
		fc_xact->blockState = TBLOCK_SUBRESTART;
	else if (fc_xact->blockState == TBLOCK_SUBABORT)
		fc_xact->blockState = TBLOCK_SUBABORT_RESTART;
	else
		elog(FATAL, "RollbackToSavepoint: unexpected state %s",
			 fc_BlockStateAsString(fc_xact->blockState));
}

/*
 * BeginInternalSubTransaction
 *		这与DefineSavepoint相同，但是它允许TBLOCK_STARTED，
 *		TBLOCK_IMPLICIT_INPROGRESS，TBLOCK_END和TBLOCK_PREPARE状态，
 *		因此可以安全地在可能在非BEGIN块内或在
 *		提交/准备时运行延迟触发器的函数中使用。 另外，它自动执行
 *		CommitTransactionCommand/StartTransactionCommand，而不是期望
 *		调用者来执行它。
 */
void BeginInternalSubTransaction(const char *fc_name)
{
	TransactionState fc_s = CurrentTransactionState;

	/*
	 * 在每个并行操作开始时，工作线程会同步事务状态，
	 * 所以我们无法在那个时间点后考虑新的子事务。
	 * 我们可能能够对通过此函数建立的子事务类型
	 * 进行例外处理，该类型通常在我们要释放或回滚
	 * 子事务之前的上下文中使用，以便不会对
	 * 事务状态造成持续的更改。 但是，当前我们禁止
	 * 这种情况以及所有其他情况。
	 */
	if (IsInParallelMode())
		ereport(ERROR,
				(errcode(ERRCODE_INVALID_TRANSACTION_STATE),
				 errmsg("cannot start subtransactions during a parallel operation")));

	switch (fc_s->blockState)
	{
		case TBLOCK_STARTED:
		case TBLOCK_INPROGRESS:
		case TBLOCK_IMPLICIT_INPROGRESS:
		case TBLOCK_END:
		case TBLOCK_PREPARE:
		case TBLOCK_SUBINPROGRESS:
			/* 正常的子事务开始 */
			fc_PushTransaction();
			fc_s = CurrentTransactionState;	/* changed by push */

			/*
			 * 保存点名称，如同 TransactionState 块本身，存在于 TopTransactionContext 中。
			 */
			if (fc_name)
				fc_s->name = MemoryContextStrdup(TopTransactionContext, fc_name);
			break;

			/* 这些情况是无效的。 */
		case TBLOCK_DEFAULT:
		case TBLOCK_BEGIN:
		case TBLOCK_PARALLEL_INPROGRESS:
		case TBLOCK_SUBBEGIN:
		case TBLOCK_SUBRELEASE:
		case TBLOCK_SUBCOMMIT:
		case TBLOCK_ABORT:
		case TBLOCK_SUBABORT:
		case TBLOCK_ABORT_END:
		case TBLOCK_SUBABORT_END:
		case TBLOCK_ABORT_PENDING:
		case TBLOCK_SUBABORT_PENDING:
		case TBLOCK_SUBRESTART:
		case TBLOCK_SUBABORT_RESTART:
			elog(FATAL, "BeginInternalSubTransaction: unexpected state %s",
				 fc_BlockStateAsString(fc_s->blockState));
			break;
	}

	CommitTransactionCommand();
	StartTransactionCommand();
}

/*
 * ReleaseCurrentSubTransaction
 *
 * 释放（即，提交）最内层子事务，无论其保存点名称
 * （如果有）。
 * 注意：不要与此一起使用CommitTransactionCommand/StartTransactionCommand。
 */
void ReleaseCurrentSubTransaction(void)
{
	TransactionState fc_s = CurrentTransactionState;

	/*
	 * 在每个并行操作开始时，工作线程会同步事务状态，
	 * 所以我们无法在那个时间点后考虑子事务的提交。
	 * 反正这也不应该发生。 调用此的代码通常会
	 * 首先调用BeginInternalSubTransaction()，在那里会失败。
	 */
	if (IsInParallelMode())
		ereport(ERROR,
				(errcode(ERRCODE_INVALID_TRANSACTION_STATE),
				 errmsg("cannot commit subtransactions during a parallel operation")));

	if (fc_s->blockState != TBLOCK_SUBINPROGRESS)
		elog(ERROR, "ReleaseCurrentSubTransaction: unexpected state %s",
			 fc_BlockStateAsString(fc_s->blockState));
	Assert(fc_s->state == TRANS_INPROGRESS);
	MemoryContextSwitchTo(CurTransactionContext);
	fc_CommitSubTransaction();
	fc_s = CurrentTransactionState;	/* changed by pop */
	Assert(fc_s->state == TRANS_INPROGRESS);
}

/*
 * RollbackAndReleaseCurrentSubTransaction
 *
 * 回滚并释放（即，撤消）最内层子事务，无论其保存点名称
 * （如果有）。
 * 注意：不要与此一起使用CommitTransactionCommand/StartTransactionCommand。
 */
void RollbackAndReleaseCurrentSubTransaction(void)
{
	TransactionState fc_s = CurrentTransactionState;

	/*
	 * 与ReleaseCurrentSubTransaction()不同，在并行操作期间
	 * 这名义上是被允许的。 这是因为我们可能在主调者中，
	 * 从在并行模式下抛出错误的情况下恢复。 我们
	 * 不会在工作线程中达到这里，因为BeginInternalSubTransaction()
	 * 会失败。
	 */

	switch (fc_s->blockState)
	{
			/* 必须在子事务中 */
		case TBLOCK_SUBINPROGRESS:
		case TBLOCK_SUBABORT:
			break;

			/* 这些情况是无效的。 */
		case TBLOCK_DEFAULT:
		case TBLOCK_STARTED:
		case TBLOCK_BEGIN:
		case TBLOCK_IMPLICIT_INPROGRESS:
		case TBLOCK_PARALLEL_INPROGRESS:
		case TBLOCK_SUBBEGIN:
		case TBLOCK_INPROGRESS:
		case TBLOCK_END:
		case TBLOCK_SUBRELEASE:
		case TBLOCK_SUBCOMMIT:
		case TBLOCK_ABORT:
		case TBLOCK_ABORT_END:
		case TBLOCK_SUBABORT_END:
		case TBLOCK_ABORT_PENDING:
		case TBLOCK_SUBABORT_PENDING:
		case TBLOCK_SUBRESTART:
		case TBLOCK_SUBABORT_RESTART:
		case TBLOCK_PREPARE:
			elog(FATAL, "RollbackAndReleaseCurrentSubTransaction: unexpected state %s",
				 fc_BlockStateAsString(fc_s->blockState));
			break;
	}

	/*
	 * 如果需要，终止当前子事务。
	 */
	if (fc_s->blockState == TBLOCK_SUBINPROGRESS)
		fc_AbortSubTransaction();

	/* 也清理它 */
	fc_CleanupSubTransaction();

	fc_s = CurrentTransactionState;	/* changed by pop */
	AssertState(fc_s->blockState == TBLOCK_SUBINPROGRESS ||
				fc_s->blockState == TBLOCK_INPROGRESS ||
				fc_s->blockState == TBLOCK_IMPLICIT_INPROGRESS ||
				fc_s->blockState == TBLOCK_STARTED);
}

/*
 *	AbortOutOfAnyTransaction
 *
 *		此例程用于错误恢复目的。 它终止任何
 *		活动事务或事务块，使系统处于已知的
 *		空闲状态。
 */
void AbortOutOfAnyTransaction(void)
{
	TransactionState fc_s = CurrentTransactionState;

	/* 确保我们没有在注定失败的内存上下文中运行 */
	fc_AtAbort_Memory();

	/*
	 * 退出任何事务或嵌套事务
	 */
	do
	{
		switch (fc_s->blockState)
		{
			case TBLOCK_DEFAULT:
				if (fc_s->state == TRANS_DEFAULT)
				{
					/* 不在事务中，什么都不做 */
				}
				else
				{
					/*
					 * 我们可以在事务开始时发生错误后到达这里
					 * （状态将为 TRANS_START）。需要清理
					 * 未完全开始的事务。首先，调整
					 * 低级状态以抑制 AbortTransaction 的警告消息。
					 */
					if (fc_s->state == TRANS_START)
						fc_s->state = TRANS_INPROGRESS;
					fc_AbortTransaction();
					fc_CleanupTransaction();
				}
				break;
			case TBLOCK_STARTED:
			case TBLOCK_BEGIN:
			case TBLOCK_INPROGRESS:
			case TBLOCK_IMPLICIT_INPROGRESS:
			case TBLOCK_PARALLEL_INPROGRESS:
			case TBLOCK_END:
			case TBLOCK_ABORT_PENDING:
			case TBLOCK_PREPARE:
				/* 在事务中，因此进行清理 */
				fc_AbortTransaction();
				fc_CleanupTransaction();
				fc_s->blockState = TBLOCK_DEFAULT;
				break;
			case TBLOCK_ABORT:
			case TBLOCK_ABORT_END:

				/*
				 * AbortTransaction 已经完成，仍然需要清理。
				 * 然而，如果我们在执行 ROLLBACK 的过程中失败，
				 * 将会有一个正在运行该命令的活动门户，我们
				 * 需要在进行 CleanupTransaction 之前关闭它。
				 */
				AtAbort_Portals();
				fc_CleanupTransaction();
				fc_s->blockState = TBLOCK_DEFAULT;
				break;

				/*
				 * 在子事务中，因此清理它并中止父事务
				 */
			case TBLOCK_SUBBEGIN:
			case TBLOCK_SUBINPROGRESS:
			case TBLOCK_SUBRELEASE:
			case TBLOCK_SUBCOMMIT:
			case TBLOCK_SUBABORT_PENDING:
			case TBLOCK_SUBRESTART:
				fc_AbortSubTransaction();
				fc_CleanupSubTransaction();
				fc_s = CurrentTransactionState;	/* changed by pop */
				break;

			case TBLOCK_SUBABORT:
			case TBLOCK_SUBABORT_END:
			case TBLOCK_SUBABORT_RESTART:
				/* 如上所述，但 AbortSubTransaction 已经完成 */
				if (fc_s->curTransactionOwner)
				{
					/* 如在 TBLOCK_ABORT 中，可能有一个活跃的门户待关闭 */
					AtSubAbort_Portals(fc_s->subTransactionId,
									   fc_s->parent->subTransactionId,
									   fc_s->curTransactionOwner,
									   fc_s->parent->curTransactionOwner);
				}
				fc_CleanupSubTransaction();
				fc_s = CurrentTransactionState;	/* changed by pop */
				break;
		}
	} while (fc_s->blockState != TBLOCK_DEFAULT);

	/* 现在应该不在所有子事务中 */
	Assert(fc_s->parent == NULL);

	/* 如果我们实际上没有需要做的事情，恢复到 TopMemoryContext */
	fc_AtCleanup_Memory();
}

/*
 * IsTransactionBlock --- 我们是否在一个事务块中？
 */
bool IsTransactionBlock(void)
{
	TransactionState fc_s = CurrentTransactionState;

	if (fc_s->blockState == TBLOCK_DEFAULT || fc_s->blockState == TBLOCK_STARTED)
		return false;

	return true;
}

/*
 * IsTransactionOrTransactionBlock --- 我们是否在一个事务
 * 或者一个事务块中？（当返回 false 时，后端实际上是“空闲”的。）
 *
 * 这应该与 IsTransactionBlock 和 IsTransactionState 匹配。
 */
bool IsTransactionOrTransactionBlock(void)
{
	TransactionState fc_s = CurrentTransactionState;

	if (fc_s->blockState == TBLOCK_DEFAULT)
		return false;

	return true;
}

/*
 * TransactionBlockStatusCode - 返回状态代码以发送到 ReadyForQuery
 */
char TransactionBlockStatusCode(void)
{
	TransactionState fc_s = CurrentTransactionState;

	switch (fc_s->blockState)
	{
		case TBLOCK_DEFAULT:
		case TBLOCK_STARTED:
			return 'I';			/* 空闲 --- 不在事务中 */
		case TBLOCK_BEGIN:
		case TBLOCK_SUBBEGIN:
		case TBLOCK_INPROGRESS:
		case TBLOCK_IMPLICIT_INPROGRESS:
		case TBLOCK_PARALLEL_INPROGRESS:
		case TBLOCK_SUBINPROGRESS:
		case TBLOCK_END:
		case TBLOCK_SUBRELEASE:
		case TBLOCK_SUBCOMMIT:
		case TBLOCK_PREPARE:
			return 'T';			/* 在事务中 */
		case TBLOCK_ABORT:
		case TBLOCK_SUBABORT:
		case TBLOCK_ABORT_END:
		case TBLOCK_SUBABORT_END:
		case TBLOCK_ABORT_PENDING:
		case TBLOCK_SUBABORT_PENDING:
		case TBLOCK_SUBRESTART:
		case TBLOCK_SUBABORT_RESTART:
			return 'E';			/* 在失败的事务中 */
	}

	/* 不应该到这里 */
	elog(FATAL, "invalid transaction block state: %s",
		 fc_BlockStateAsString(fc_s->blockState));
	return 0;					/* 保持编译器安静 */
}

/*
 * IsSubTransaction
 */
bool IsSubTransaction(void)
{
	TransactionState fc_s = CurrentTransactionState;

	if (fc_s->nestingLevel >= 2)
		return true;

	return false;
}

/*
 * StartSubTransaction
 *
 * 如果你在想为什么这与 PushTransaction 分开：这是因为
 * 我们无法方便地在 DefineSavepoint 中直接做这些事情。
 * SAVEPOINT 实用命令将在一个门户内执行，如果我们
 * 捣乱 CurrentMemoryContext 或 CurrentResourceOwner，那么退出
 * 该门户将撤消这些设置。因此，我们使 DefineSavepoint 只是
 * 推送一个虚拟事务块，当控制返回到主
 * 空闲循环时，将调用 CommitTransactionCommand，然后我们将来到这里
 * 完成启动子事务。
 */
static void fc_StartSubTransaction(void)
{
	TransactionState fc_s = CurrentTransactionState;

	if (fc_s->state != TRANS_DEFAULT)
		elog(WARNING, "StartSubTransaction while in %s state",
			 fc_TransStateAsString(fc_s->state));

	fc_s->state = TRANS_START;

	/*
	 * 为新的子事务初始化子系统
	 *
	 * 必须首先初始化资源管理相关内容
	 */
	fc_AtSubStart_Memory();
	fc_AtSubStart_ResourceOwner();
	AfterTriggerBeginSubXact();

	fc_s->state = TRANS_INPROGRESS;

	/*
	 * 调用子事务开始的回调
	 */
	fc_CallSubXactCallbacks(SUBXACT_EVENT_START_SUB, fc_s->subTransactionId,
						 fc_s->parent->subTransactionId);

	fc_ShowTransactionState("StartSubTransaction");
}

/*
 * CommitSubTransaction
 *
 * 调用者必须确保在调用此函数后如果有其
 * 本地指针，始终重新分配 CurrentTransactionState。
 */
static void fc_CommitSubTransaction(void)
{
	TransactionState fc_s = CurrentTransactionState;

	fc_ShowTransactionState("CommitSubTransaction");

	if (fc_s->state != TRANS_INPROGRESS)
		elog(WARNING, "CommitSubTransaction while in %s state",
			 fc_TransStateAsString(fc_s->state));

	/* 提交前处理在这里进行 */

	fc_CallSubXactCallbacks(SUBXACT_EVENT_PRE_COMMIT_SUB, fc_s->subTransactionId,
						 fc_s->parent->subTransactionId);

	/* 如果处于并行模式，清理工作者并退出并行模式。 */
	if (IsInParallelMode())
	{
		AtEOSubXact_Parallel(true, fc_s->subTransactionId);
		fc_s->parallelModeLevel = 0;
	}

	/* 执行实际的“提交”，尽可能做到这一点 */
	fc_s->state = TRANS_COMMIT;

	/* 必须 CCI 以确保子事务的命令被视为完成 */
	CommandCounterIncrement();

	/*
	 * 在 8.4 之前，我们在此时在 clog 中标记子提交。现在只有在
	 * 顶级提交或中止的原子更新整个事务树时，才会执行此步骤（如果需要）。
	 */

	/* 提交后清理 */
	if (FullTransactionIdIsValid(fc_s->fullTransactionId))
		fc_AtSubCommit_childXids();
	AfterTriggerEndSubXact(true);
	AtSubCommit_Portals(fc_s->subTransactionId,
						fc_s->parent->subTransactionId,
						fc_s->parent->nestingLevel,
						fc_s->parent->curTransactionOwner);
	AtEOSubXact_LargeObject(true, fc_s->subTransactionId,
							fc_s->parent->subTransactionId);
	AtSubCommit_Notify();

	fc_CallSubXactCallbacks(SUBXACT_EVENT_COMMIT_SUB, fc_s->subTransactionId,
						 fc_s->parent->subTransactionId);

	ResourceOwnerRelease(fc_s->curTransactionOwner,
						 RESOURCE_RELEASE_BEFORE_LOCKS,
						 true, false);
	AtEOSubXact_RelationCache(true, fc_s->subTransactionId,
							  fc_s->parent->subTransactionId);
	AtEOSubXact_Inval(true);
	AtSubCommit_smgr();

	/*
	 * 我们实际释放的唯一锁是子事务XID锁。
	 */
	CurrentResourceOwner = fc_s->curTransactionOwner;
	if (FullTransactionIdIsValid(fc_s->fullTransactionId))
		XactLockTableDelete(XidFromFullTransactionId(fc_s->fullTransactionId));

	/*
	 * 其他锁应转移到其父资源所有者。
	 */
	ResourceOwnerRelease(fc_s->curTransactionOwner,
						 RESOURCE_RELEASE_LOCKS,
						 true, false);
	ResourceOwnerRelease(fc_s->curTransactionOwner,
						 RESOURCE_RELEASE_AFTER_LOCKS,
						 true, false);

	AtEOXact_GUC(true, fc_s->gucNestLevel);
	AtEOSubXact_SPI(true, fc_s->subTransactionId);
	AtEOSubXact_on_commit_actions(true, fc_s->subTransactionId,
								  fc_s->parent->subTransactionId);
	AtEOSubXact_Namespace(true, fc_s->subTransactionId,
						  fc_s->parent->subTransactionId);
	AtEOSubXact_Files(true, fc_s->subTransactionId,
					  fc_s->parent->subTransactionId);
	AtEOSubXact_HashTables(true, fc_s->nestingLevel);
	AtEOSubXact_PgStat(true, fc_s->nestingLevel);
	AtSubCommit_Snapshot(fc_s->nestingLevel);

	/*
	 * 我们需要恢复上层事务的只读状态，以防上层是读写的而子层是只读的；GUC会错误地认为应该保留子层状态。
	 */
	XactReadOnly = fc_s->prevXactReadOnly;

	CurrentResourceOwner = fc_s->parent->curTransactionOwner;
	CurTransactionResourceOwner = fc_s->parent->curTransactionOwner;
	ResourceOwnerDelete(fc_s->curTransactionOwner);
	fc_s->curTransactionOwner = NULL;

	fc_AtSubCommit_Memory();

	fc_s->state = TRANS_DEFAULT;

	fc_PopTransaction();
}

/*
 * AbortSubTransaction
 */
static void fc_AbortSubTransaction(void)
{
	TransactionState fc_s = CurrentTransactionState;

	/* 在清理时防止取消/死亡中断 */
	HOLD_INTERRUPTS();

	/* 确保我们有一个有效的内存上下文和资源所有者 */
	fc_AtSubAbort_Memory();
	fc_AtSubAbort_ResourceOwner();

	/*
	 * 尽快释放我们可能持有的任何LW锁。
	 * （常规锁，然而，必须在我们完成中止之前持有。）
	 * 释放LW锁是至关重要的，因为在清理期间我们可能会尝试再次获取它们！
	 *
	 * FIXME 这可能是不正确的 --- 我们是否应该保留某些锁？
	 * 比如缓冲区锁？ 我不这么认为，但我不确定。
	 */
	LWLockReleaseAll();

	pgstat_report_wait_end();
	pgstat_progress_end_command();
	AbortBufferIO();
	UnlockBuffers();

	/* 重置WAL记录构造状态 */
	XLogResetInsertion();

	/* 取消条件变量休眠 */
	ConditionVariableCancelSleep();

	/*
	 * 还要清理任何等待锁，因为如果我们在这样做之前尝试等待另一个锁，
	 * 锁管理器会出现问题。
	 */
	LockErrorCleanup();

	/*
	 * 如果还有任何超时事件处于活动状态，请确保计划超时中断。
	 * 这涵盖了由于从SIGINT处理程序中longjmp而可能导致的超时中断丢失
	 * （请参阅handle_sig_alarm中的注释）。
	 * 我们将其延迟到LockErrorCleanup之后，以便不无谓地重新调度锁或死锁检查超时。
	 */
	reschedule_timeouts();

	/*
	 * 重新启用信号，以防我们通过从信号处理程序longjmp而到达这里。
	 * 我们在这个序列中相对较早地进行此操作，以便在中止时如有需要，
	 * 超时基础设施将能够正常工作。
	 */
	PG_SETMASK(&UnBlockSig);

	/*
	 * 检查当前事务状态
	 */
	fc_ShowTransactionState("AbortSubTransaction");

	if (fc_s->state != TRANS_INPROGRESS)
		elog(WARNING, "AbortSubTransaction while in %s state",
			 fc_TransStateAsString(fc_s->state));

	fc_s->state = TRANS_ABORT;

	/*
	 * 重置可能已临时更改的用户ID。（参见AbortTransaction中的说明。）
	 */
	SetUserIdAndSecContext(fc_s->prevUser, fc_s->prevSecContext);

	/* 忘记任何活动的REINDEX。 */
	ResetReindexState(fc_s->nestingLevel);

	/* 重置逻辑流状态。 */
	ResetLogicalStreamingState();

	/*
	 * 在这里不需要SnapBuildResetExportedSnapshotState()，快照
	 * 导出在子事务中不支持。
	 */

	/* 如果必要，退出并行模式。 */
	if (IsInParallelMode())
	{
		AtEOSubXact_Parallel(false, fc_s->subTransactionId);
		fc_s->parallelModeLevel = 0;
	}

	/*
	 * 如果子事务在创建资源所有者之前失败，我们可以跳过所有这些内容...
	 */
	if (fc_s->curTransactionOwner)
	{
		AfterTriggerEndSubXact(false);
		AtSubAbort_Portals(fc_s->subTransactionId,
						   fc_s->parent->subTransactionId,
						   fc_s->curTransactionOwner,
						   fc_s->parent->curTransactionOwner);
		AtEOSubXact_LargeObject(false, fc_s->subTransactionId,
								fc_s->parent->subTransactionId);
		AtSubAbort_Notify();

		/* 宣告我们在pg_xact中中止的事实。 */
		(void) fc_RecordTransactionAbort(true);

		/* 中止后的清理 */
		if (FullTransactionIdIsValid(fc_s->fullTransactionId))
			fc_AtSubAbort_childXids();

		fc_CallSubXactCallbacks(SUBXACT_EVENT_ABORT_SUB, fc_s->subTransactionId,
							 fc_s->parent->subTransactionId);

		ResourceOwnerRelease(fc_s->curTransactionOwner,
							 RESOURCE_RELEASE_BEFORE_LOCKS,
							 false, false);
		AtEOSubXact_RelationCache(false, fc_s->subTransactionId,
								  fc_s->parent->subTransactionId);
		AtEOSubXact_Inval(false);
		ResourceOwnerRelease(fc_s->curTransactionOwner,
							 RESOURCE_RELEASE_LOCKS,
							 false, false);
		ResourceOwnerRelease(fc_s->curTransactionOwner,
							 RESOURCE_RELEASE_AFTER_LOCKS,
							 false, false);
		AtSubAbort_smgr();

		AtEOXact_GUC(false, fc_s->gucNestLevel);
		AtEOSubXact_SPI(false, fc_s->subTransactionId);
		AtEOSubXact_on_commit_actions(false, fc_s->subTransactionId,
									  fc_s->parent->subTransactionId);
		AtEOSubXact_Namespace(false, fc_s->subTransactionId,
							  fc_s->parent->subTransactionId);
		AtEOSubXact_Files(false, fc_s->subTransactionId,
						  fc_s->parent->subTransactionId);
		AtEOSubXact_HashTables(false, fc_s->nestingLevel);
		AtEOSubXact_PgStat(false, fc_s->nestingLevel);
		AtSubAbort_Snapshot(fc_s->nestingLevel);
	}

	/*
	 * 也恢复上层事务的只读状态。这应该与GUC的清理冗余，但我们也可以为了与提交情况的一致性这样做。
	 */
	XactReadOnly = fc_s->prevXactReadOnly;

	RESUME_INTERRUPTS();
}

/*
 * CleanupSubTransaction
 *
 *	调用者必须确保在调用此函数后，如果它对CurrentTransactionState有本地指针，则始终重新分配它。
 */
static void fc_CleanupSubTransaction(void)
{
	TransactionState fc_s = CurrentTransactionState;

	fc_ShowTransactionState("CleanupSubTransaction");

	if (fc_s->state != TRANS_ABORT)
		elog(WARNING, "CleanupSubTransaction while in %s state",
			 fc_TransStateAsString(fc_s->state));

	AtSubCleanup_Portals(fc_s->subTransactionId);

	CurrentResourceOwner = fc_s->parent->curTransactionOwner;
	CurTransactionResourceOwner = fc_s->parent->curTransactionOwner;
	if (fc_s->curTransactionOwner)
		ResourceOwnerDelete(fc_s->curTransactionOwner);
	fc_s->curTransactionOwner = NULL;

	fc_AtSubCleanup_Memory();

	fc_s->state = TRANS_DEFAULT;

	fc_PopTransaction();
}

/*
 * PushTransaction
 *	 为子事务创建事务状态栈条目
 *
 *	调用者必须确保在调用此函数后，如果它对CurrentTransactionState有本地指针，则始终重新分配它。
 */
static void fc_PushTransaction(void)
{
	TransactionState fc_p = CurrentTransactionState;
	TransactionState fc_s;

	/*
	 * 我们在 TopTransactionContext 中保留子事务状态节点。
	 */
	fc_s = (TransactionState)
		MemoryContextAllocZero(TopTransactionContext,
							   sizeof(TransactionStateData));

	/*
	 * 分配子事务 ID，注意计数器回绕。
	 */
	currentSubTransactionId += 1;
	if (currentSubTransactionId == InvalidSubTransactionId)
	{
		currentSubTransactionId -= 1;
		pfree(fc_s);
		ereport(ERROR,
				(errcode(ERRCODE_PROGRAM_LIMIT_EXCEEDED),
				 errmsg("cannot have more than 2^32-1 subtransactions in a transaction")));
	}

	/*
	 * 现在我们可以堆叠一个最小有效的子事务，而不用担心失败。
	 */
	fc_s->fullTransactionId = InvalidFullTransactionId;	/* 直到分配 */
	fc_s->subTransactionId = currentSubTransactionId;
	fc_s->parent = fc_p;
	fc_s->nestingLevel = fc_p->nestingLevel + 1;
	fc_s->gucNestLevel = NewGUCNestLevel();
	fc_s->savepointLevel = fc_p->savepointLevel;
	fc_s->state = TRANS_DEFAULT;
	fc_s->blockState = TBLOCK_SUBBEGIN;
	GetUserIdAndSecContext(&fc_s->prevUser, &fc_s->prevSecContext);
	fc_s->prevXactReadOnly = XactReadOnly;
	fc_s->startedInRecovery = fc_p->startedInRecovery;
	fc_s->parallelModeLevel = 0;
	fc_s->topXidLogged = false;

	CurrentTransactionState = fc_s;

	/*
	 * AbortSubTransaction 和 CleanupSubTransaction 必须能够处理
	 * 从现在开始的子事务；特别是它们不应该假设它一定有事务上下文、资源拥有者或 XID。
	 */
}

/*
 * PopTransaction
 *		返回到父事务状态
 *
 *	调用者必须确保在调用此函数后，如果它有对 CurrentTransactionState 的本地指针，
 *	则始终重新分配。
 */
static void fc_PopTransaction(void)
{
	TransactionState fc_s = CurrentTransactionState;

	if (fc_s->state != TRANS_DEFAULT)
		elog(WARNING, "PopTransaction while in %s state",
			 fc_TransStateAsString(fc_s->state));

	if (fc_s->parent == NULL)
		elog(FATAL, "PopTransaction with no parent");

	CurrentTransactionState = fc_s->parent;

	/* 让我们确保 CurTransactionContext 是良好的 */
	CurTransactionContext = fc_s->parent->curTransactionContext;
	MemoryContextSwitchTo(CurTransactionContext);

	/* ResourceOwner 链接也是如此 */
	CurTransactionResourceOwner = fc_s->parent->curTransactionOwner;
	CurrentResourceOwner = fc_s->parent->curTransactionOwner;

	/* 释放旧的子结构 */
	if (fc_s->name)
		pfree(fc_s->name);
	pfree(fc_s);
}

/*
 * EstimateTransactionStateSpace
 *		估计 SerializeTransactionState 需要的空间量。
 *		稍微高估是可以的，但我们可以很简单地计算出精确值，所以我们这样做。
 */
Size EstimateTransactionStateSpace(void)
{
	TransactionState fc_s;
	Size		fc_nxids = 0;
	Size		fc_size = SerializedTransactionStateHeaderSize;

	for (fc_s = CurrentTransactionState; fc_s != NULL; fc_s = fc_s->parent)
	{
		if (FullTransactionIdIsValid(fc_s->fullTransactionId))
			fc_nxids = add_size(fc_nxids, 1);
		fc_nxids = add_size(fc_nxids, fc_s->nChildXids);
	}

	return add_size(fc_size, mul_size(sizeof(TransactionId), fc_nxids));
}

/*
 * SerializeTransactionState
 *		写出我们事务状态的相关细节，这些细节将由并行工作者使用。
 *
 * 我们需要保存和恢复 XactDeferrable、XactIsoLevel 和与此事务相关的 XIDs。
 * 这些被序列化到一个 caller-supplied 的缓冲区，该缓冲区足够大，可以容纳
 * EstimateTransactionStateSpace() 报告的字节数。我们将 XIDs 以排序顺序发出，以方便接收过程。
 */
void SerializeTransactionState(Size fc_maxsize, char *fc_start_address)
{
	TransactionState fc_s;
	Size		fc_nxids = 0;
	Size		fc_i = 0;
	TransactionId *fc_workspace;
	SerializedTransactionState *fc_result;

	fc_result = (SerializedTransactionState *) fc_start_address;

	fc_result->xactIsoLevel = XactIsoLevel;
	fc_result->xactDeferrable = XactDeferrable;
	fc_result->topFullTransactionId = XactTopFullTransactionId;
	fc_result->currentFullTransactionId =
		CurrentTransactionState->fullTransactionId;
	fc_result->currentCommandId = currentCommandId;

	/*
	 * 如果我们在并行工作者中运行并启动我们自己的并行工作者，
	 * 我们可以直接传递给我们的信息。
	 */
	if (nParallelCurrentXids > 0)
	{
		fc_result->nParallelCurrentXids = nParallelCurrentXids;
		memcpy(&fc_result->parallelCurrentXids[0], ParallelCurrentXids,
			   nParallelCurrentXids * sizeof(TransactionId));
		return;
	}

	/*
	 * 好的，我们需要生成一个我们的工作者应该视为当前的 XIDs 排序列表。
	 * 首先，计算它们的数量。
	 */
	for (fc_s = CurrentTransactionState; fc_s != NULL; fc_s = fc_s->parent)
	{
		if (FullTransactionIdIsValid(fc_s->fullTransactionId))
			fc_nxids = add_size(fc_nxids, 1);
		fc_nxids = add_size(fc_nxids, fc_s->nChildXids);
	}
	Assert(SerializedTransactionStateHeaderSize + fc_nxids * sizeof(TransactionId)
		   <= fc_maxsize);

	/* 将它们复制到我们的临时空间。 */
	fc_workspace = palloc(fc_nxids * sizeof(TransactionId));
	for (fc_s = CurrentTransactionState; fc_s != NULL; fc_s = fc_s->parent)
	{
		if (FullTransactionIdIsValid(fc_s->fullTransactionId))
			fc_workspace[fc_i++] = XidFromFullTransactionId(fc_s->fullTransactionId);
		if (fc_s->nChildXids > 0)
			memcpy(&fc_workspace[fc_i], fc_s->childXids,
				   fc_s->nChildXids * sizeof(TransactionId));
		fc_i += fc_s->nChildXids;
	}
	Assert(fc_i == fc_nxids);

	/* 对它们进行排序。 */
	qsort(fc_workspace, fc_nxids, sizeof(TransactionId), xidComparator);

	/* 将数据复制到输出区域。 */
	fc_result->nParallelCurrentXids = fc_nxids;
	memcpy(&fc_result->parallelCurrentXids[0], fc_workspace,
		   fc_nxids * sizeof(TransactionId));
}

/*
 * StartParallelWorkerTransaction
 *		开始一个并行工作者事务，恢复由 SerializeTransactionState 序列化的相关事务状态。
 */
void StartParallelWorkerTransaction(char *fc_tstatespace)
{
	SerializedTransactionState *fc_tstate;

	Assert(CurrentTransactionState->blockState == TBLOCK_DEFAULT);
	fc_StartTransaction();

	fc_tstate = (SerializedTransactionState *) fc_tstatespace;
	XactIsoLevel = fc_tstate->xactIsoLevel;
	XactDeferrable = fc_tstate->xactDeferrable;
	XactTopFullTransactionId = fc_tstate->topFullTransactionId;
	CurrentTransactionState->fullTransactionId =
		fc_tstate->currentFullTransactionId;
	currentCommandId = fc_tstate->currentCommandId;
	nParallelCurrentXids = fc_tstate->nParallelCurrentXids;
	ParallelCurrentXids = &fc_tstate->parallelCurrentXids[0];

	CurrentTransactionState->blockState = TBLOCK_PARALLEL_INPROGRESS;
}

/*
 * EndParallelWorkerTransaction
 *		结束一个并行工作者事务。
 */
void EndParallelWorkerTransaction(void)
{
	Assert(CurrentTransactionState->blockState == TBLOCK_PARALLEL_INPROGRESS);
	fc_CommitTransaction();
	CurrentTransactionState->blockState = TBLOCK_DEFAULT;
}

/*
 * ShowTransactionState
 *		调试支持
 */
static void fc_ShowTransactionState(const char *fc_str)
{
	/* 如果消息肯定不会打印，则跳过工作 */
	if (message_level_is_interesting(DEBUG5))
		fc_ShowTransactionStateRec(fc_str, CurrentTransactionState);
}

/*
 * ShowTransactionStateRec
 *		ShowTransactionState 的递归子例程
 */
static void fc_ShowTransactionStateRec(const char *fc_str, TransactionState fc_s)
{
	StringInfoData fc_buf;

	initStringInfo(&fc_buf);

	if (fc_s->nChildXids > 0)
	{
		int			fc_i;

		appendStringInfo(&fc_buf, ", children: %u", fc_s->childXids[0]);
		for (fc_i = 1; fc_i < fc_s->nChildXids; fc_i++)
			appendStringInfo(&fc_buf, " %u", fc_s->childXids[fc_i]);
	}

	if (fc_s->parent)
		fc_ShowTransactionStateRec(fc_str, fc_s->parent);

	ereport(DEBUG5,
			(errmsg_internal("%s(%d) name: %s; blockState: %s; state: %s, xid/subid/cid: %u/%u/%u%s%s",
							 fc_str, fc_s->nestingLevel,
							 PointerIsValid(fc_s->name) ? fc_s->name : "unnamed",
							 fc_BlockStateAsString(fc_s->blockState),
							 fc_TransStateAsString(fc_s->state),
							 (unsigned int) XidFromFullTransactionId(fc_s->fullTransactionId),
							 (unsigned int) fc_s->subTransactionId,
							 (unsigned int) currentCommandId,
							 currentCommandIdUsed ? " (used)" : "",
							 fc_buf.data)));

	pfree(fc_buf.data);
}

/*
 * BlockStateAsString
 *		调试支持
 */
static const char * fc_BlockStateAsString(TBlockState fc_blockState)
{
	switch (fc_blockState)
	{
		case TBLOCK_DEFAULT:
			return "DEFAULT";
		case TBLOCK_STARTED:
			return "STARTED";
		case TBLOCK_BEGIN:
			return "BEGIN";
		case TBLOCK_INPROGRESS:
			return "INPROGRESS";
		case TBLOCK_IMPLICIT_INPROGRESS:
			return "IMPLICIT_INPROGRESS";
		case TBLOCK_PARALLEL_INPROGRESS:
			return "PARALLEL_INPROGRESS";
		case TBLOCK_END:
			return "END";
		case TBLOCK_ABORT:
			return "ABORT";
		case TBLOCK_ABORT_END:
			return "ABORT_END";
		case TBLOCK_ABORT_PENDING:
			return "ABORT_PENDING";
		case TBLOCK_PREPARE:
			return "PREPARE";
		case TBLOCK_SUBBEGIN:
			return "SUBBEGIN";
		case TBLOCK_SUBINPROGRESS:
			return "SUBINPROGRESS";
		case TBLOCK_SUBRELEASE:
			return "SUBRELEASE";
		case TBLOCK_SUBCOMMIT:
			return "SUBCOMMIT";
		case TBLOCK_SUBABORT:
			return "SUBABORT";
		case TBLOCK_SUBABORT_END:
			return "SUBABORT_END";
		case TBLOCK_SUBABORT_PENDING:
			return "SUBABORT_PENDING";
		case TBLOCK_SUBRESTART:
			return "SUBRESTART";
		case TBLOCK_SUBABORT_RESTART:
			return "SUBABORT_RESTART";
	}
	return "UNRECOGNIZED";
}

/*
 * TransStateAsString
 *		调试支持
 */
static const char * fc_TransStateAsString(TransState fc_state)
{
	switch (fc_state)
	{
		case TRANS_DEFAULT:
			return "DEFAULT";
		case TRANS_START:
			return "START";
		case TRANS_INPROGRESS:
			return "INPROGRESS";
		case TRANS_COMMIT:
			return "COMMIT";
		case TRANS_ABORT:
			return "ABORT";
		case TRANS_PREPARE:
			return "PREPARE";
	}
	return "UNRECOGNIZED";
}

/*
 * xactGetCommittedChildren
 *
 * 获取当前事务的已提交子事务列表。返回值是子事务的数量。*ptr 被设置为指向一个
 * TransactionIds 数组。该数组在 TopTransactionContext 中分配；
 * 调用者不应对其执行 pfree()（这是与 8.4 之前代码的一个变化！）。
 * 如果没有子事务，*ptr 被设置为 NULL。
 */
int xactGetCommittedChildren(TransactionId **fc_ptr)
{
	TransactionState fc_s = CurrentTransactionState;

	if (fc_s->nChildXids == 0)
		*fc_ptr = NULL;
	else
		*fc_ptr = fc_s->childXids;

	return fc_s->nChildXids;
}

/*
 * XLOG 支持例程
 */


/*
 * 记录普通或两阶段事务提交的提交记录。
 *
 * 当 twophase_xid 有效时将发出 2pc 提交，否则将发出普通提交。
 */
XLogRecPtr XactLogCommitRecord(TimestampTz fc_commit_time,
					int fc_nsubxacts, TransactionId *fc_subxacts,
					int fc_nrels, RelFileNode *fc_rels,
					int fc_ndroppedstats, xl_xact_stats_item *fc_droppedstats,
					int fc_nmsgs, SharedInvalidationMessage *fc_msgs,
					bool fc_relcacheInval,
					int fc_xactflags, TransactionId fc_twophase_xid,
					const char *fc_twophase_gid)
{
	xl_xact_commit fc_xlrec;
	xl_xact_xinfo fc_xl_xinfo;
	xl_xact_dbinfo fc_xl_dbinfo;
	xl_xact_subxacts fc_xl_subxacts;
	xl_xact_relfilenodes fc_xl_relfilenodes;
	xl_xact_stats_items fc_xl_dropped_stats;
	xl_xact_invals fc_xl_invals;
	xl_xact_twophase fc_xl_twophase;
	xl_xact_origin fc_xl_origin;
	uint8		fc_info;

	Assert(CritSectionCount > 0);

	fc_xl_xinfo.xinfo = 0;

	/* 在普通和 2pc 提交之间进行决策 */
	if (!TransactionIdIsValid(fc_twophase_xid))
		fc_info = XLOG_XACT_COMMIT;
	else
		fc_info = XLOG_XACT_COMMIT_PREPARED;

	/* 首先确定并收集所需的所有信息 */

	fc_xlrec.xact_time = fc_commit_time;

	if (fc_relcacheInval)
		fc_xl_xinfo.xinfo |= XACT_COMPLETION_UPDATE_RELCACHE_FILE;
	if (forceSyncCommit)
		fc_xl_xinfo.xinfo |= XACT_COMPLETION_FORCE_SYNC_COMMIT;
	if ((fc_xactflags & XACT_FLAGS_ACQUIREDACCESSEXCLUSIVELOCK))
		fc_xl_xinfo.xinfo |= XACT_XINFO_HAS_AE_LOCKS;

	/*
	 * 检查调用者是否希望在应用此提交后立即请求备份的反馈。
	 */
	if (synchronous_commit >= SYNCHRONOUS_COMMIT_REMOTE_APPLY)
		fc_xl_xinfo.xinfo |= XACT_COMPLETION_APPLY_FEEDBACK;

	/*
	 * Relcache 无效化需要有关当前数据库的信息，逻辑解码也是如此。
	 */
	if (fc_nmsgs > 0 || XLogLogicalInfoActive())
	{
		fc_xl_xinfo.xinfo |= XACT_XINFO_HAS_DBINFO;
		fc_xl_dbinfo.dbId = MyDatabaseId;
		fc_xl_dbinfo.tsId = MyDatabaseTableSpace;
	}

	if (fc_nsubxacts > 0)
	{
		fc_xl_xinfo.xinfo |= XACT_XINFO_HAS_SUBXACTS;
		fc_xl_subxacts.nsubxacts = fc_nsubxacts;
	}

	if (fc_nrels > 0)
	{
		fc_xl_xinfo.xinfo |= XACT_XINFO_HAS_RELFILENODES;
		fc_xl_relfilenodes.nrels = fc_nrels;
		fc_info |= XLR_SPECIAL_REL_UPDATE;
	}

	if (fc_ndroppedstats > 0)
	{
		fc_xl_xinfo.xinfo |= XACT_XINFO_HAS_DROPPED_STATS;
		fc_xl_dropped_stats.nitems = fc_ndroppedstats;
	}

	if (fc_nmsgs > 0)
	{
		fc_xl_xinfo.xinfo |= XACT_XINFO_HAS_INVALS;
		fc_xl_invals.nmsgs = fc_nmsgs;
	}

	if (TransactionIdIsValid(fc_twophase_xid))
	{
		fc_xl_xinfo.xinfo |= XACT_XINFO_HAS_TWOPHASE;
		fc_xl_twophase.xid = fc_twophase_xid;
		Assert(fc_twophase_gid != NULL);

		if (XLogLogicalInfoActive())
			fc_xl_xinfo.xinfo |= XACT_XINFO_HAS_GID;
	}

	/* 转储事务来源信息 */
	if (replorigin_session_origin != InvalidRepOriginId)
	{
		fc_xl_xinfo.xinfo |= XACT_XINFO_HAS_ORIGIN;

		fc_xl_origin.origin_lsn = replorigin_session_origin_lsn;
		fc_xl_origin.origin_timestamp = replorigin_session_origin_timestamp;
	}

#ifdef FDDQ
		if (!enable_logical_replication)
		{
			fc_xl_xinfo.xinfo |= XACT_XINFO_LOGIREPL_OFF;
		}
#endif


	if (fc_xl_xinfo.xinfo != 0)
		fc_info |= XLOG_XACT_HAS_INFO;

	/* 然后将所有收集的数据包含到提交记录中。 */

	XLogBeginInsert();

	XLogRegisterData((char *) (&fc_xlrec), sizeof(xl_xact_commit));

	if (fc_xl_xinfo.xinfo != 0)
		XLogRegisterData((char *) (&fc_xl_xinfo.xinfo), sizeof(fc_xl_xinfo.xinfo));

	if (fc_xl_xinfo.xinfo & XACT_XINFO_HAS_DBINFO)
		XLogRegisterData((char *) (&fc_xl_dbinfo), sizeof(fc_xl_dbinfo));

	if (fc_xl_xinfo.xinfo & XACT_XINFO_HAS_SUBXACTS)
	{
		XLogRegisterData((char *) (&fc_xl_subxacts),
						 MinSizeOfXactSubxacts);
		XLogRegisterData((char *) fc_subxacts,
						 fc_nsubxacts * sizeof(TransactionId));
	}

	if (fc_xl_xinfo.xinfo & XACT_XINFO_HAS_RELFILENODES)
	{
		XLogRegisterData((char *) (&fc_xl_relfilenodes),
						 MinSizeOfXactRelfilenodes);
		XLogRegisterData((char *) fc_rels,
						 fc_nrels * sizeof(RelFileNode));
	}

	if (fc_xl_xinfo.xinfo & XACT_XINFO_HAS_DROPPED_STATS)
	{
		XLogRegisterData((char *) (&fc_xl_dropped_stats),
						 MinSizeOfXactStatsItems);
		XLogRegisterData((char *) fc_droppedstats,
						 fc_ndroppedstats * sizeof(xl_xact_stats_item));
	}

	if (fc_xl_xinfo.xinfo & XACT_XINFO_HAS_INVALS)
	{
		XLogRegisterData((char *) (&fc_xl_invals), MinSizeOfXactInvals);
		XLogRegisterData((char *) fc_msgs,
						 fc_nmsgs * sizeof(SharedInvalidationMessage));
	}

	if (fc_xl_xinfo.xinfo & XACT_XINFO_HAS_TWOPHASE)
	{
		XLogRegisterData((char *) (&fc_xl_twophase), sizeof(xl_xact_twophase));
		if (fc_xl_xinfo.xinfo & XACT_XINFO_HAS_GID)
			XLogRegisterData(unconstify(char *, fc_twophase_gid), strlen(fc_twophase_gid) + 1);
	}

	if (fc_xl_xinfo.xinfo & XACT_XINFO_HAS_ORIGIN)
		XLogRegisterData((char *) (&fc_xl_origin), sizeof(xl_xact_origin));

	/* 我们允许按 xacts 进行过滤 */
	XLogSetRecordFlags(XLOG_INCLUDE_ORIGIN);

	return XLogInsert(RM_XACT_ID, fc_info);
}

/*
 * 记录普通或两阶段事务中止的中止记录。
 *
 * 当 twophase_xid 有效时将发出 2pc 中止，否则将发出普通中止。
 */
XLogRecPtr XactLogAbortRecord(TimestampTz fc_abort_time,
				   int fc_nsubxacts, TransactionId *fc_subxacts,
				   int fc_nrels, RelFileNode *fc_rels,
				   int fc_ndroppedstats, xl_xact_stats_item *fc_droppedstats,
				   int fc_xactflags, TransactionId fc_twophase_xid,
				   const char *fc_twophase_gid)
{
	xl_xact_abort fc_xlrec;
	xl_xact_xinfo fc_xl_xinfo;
	xl_xact_subxacts fc_xl_subxacts;
	xl_xact_relfilenodes fc_xl_relfilenodes;
	xl_xact_stats_items fc_xl_dropped_stats;
	xl_xact_twophase fc_xl_twophase;
	xl_xact_dbinfo fc_xl_dbinfo;
	xl_xact_origin fc_xl_origin;

	uint8		fc_info;

	Assert(CritSectionCount > 0);

	fc_xl_xinfo.xinfo = 0;

	/* 在普通和 2pc 中止之间进行决策 */
	if (!TransactionIdIsValid(fc_twophase_xid))
		fc_info = XLOG_XACT_ABORT;
	else
		fc_info = XLOG_XACT_ABORT_PREPARED;


	/* 首先确定并收集所需的所有信息 */

	fc_xlrec.xact_time = fc_abort_time;

	if ((fc_xactflags & XACT_FLAGS_ACQUIREDACCESSEXCLUSIVELOCK))
		fc_xl_xinfo.xinfo |= XACT_XINFO_HAS_AE_LOCKS;

	if (fc_nsubxacts > 0)
	{
		fc_xl_xinfo.xinfo |= XACT_XINFO_HAS_SUBXACTS;
		fc_xl_subxacts.nsubxacts = fc_nsubxacts;
	}

	if (fc_nrels > 0)
	{
		fc_xl_xinfo.xinfo |= XACT_XINFO_HAS_RELFILENODES;
		fc_xl_relfilenodes.nrels = fc_nrels;
		fc_info |= XLR_SPECIAL_REL_UPDATE;
	}

	if (fc_ndroppedstats > 0)
	{
		fc_xl_xinfo.xinfo |= XACT_XINFO_HAS_DROPPED_STATS;
		fc_xl_dropped_stats.nitems = fc_ndroppedstats;
	}

	if (TransactionIdIsValid(fc_twophase_xid))
	{
		fc_xl_xinfo.xinfo |= XACT_XINFO_HAS_TWOPHASE;
		fc_xl_twophase.xid = fc_twophase_xid;
		Assert(fc_twophase_gid != NULL);

		if (XLogLogicalInfoActive())
			fc_xl_xinfo.xinfo |= XACT_XINFO_HAS_GID;
	}

	if (TransactionIdIsValid(fc_twophase_xid) && XLogLogicalInfoActive())
	{
		fc_xl_xinfo.xinfo |= XACT_XINFO_HAS_DBINFO;
		fc_xl_dbinfo.dbId = MyDatabaseId;
		fc_xl_dbinfo.tsId = MyDatabaseTableSpace;
	}

	/*
	 * 仅在中止已准备的情况下转储事务来源信息。我们在恢复期间
	 * 需要此信息以更新复制来源进度。
	 */
	if ((replorigin_session_origin != InvalidRepOriginId) &&
		TransactionIdIsValid(fc_twophase_xid))
	{
		fc_xl_xinfo.xinfo |= XACT_XINFO_HAS_ORIGIN;

		fc_xl_origin.origin_lsn = replorigin_session_origin_lsn;
		fc_xl_origin.origin_timestamp = replorigin_session_origin_timestamp;
	}

#ifdef FDDQ
	if (!enable_logical_replication)
	{
		fc_xl_xinfo.xinfo |= XACT_XINFO_LOGIREPL_OFF;
	}
#endif

	if (fc_xl_xinfo.xinfo != 0)
		fc_info |= XLOG_XACT_HAS_INFO;

	/* 然后将所有收集的数据包含到中止记录中。 */

	XLogBeginInsert();

	XLogRegisterData((char *) (&fc_xlrec), MinSizeOfXactAbort);

	if (fc_xl_xinfo.xinfo != 0)
		XLogRegisterData((char *) (&fc_xl_xinfo), sizeof(fc_xl_xinfo));

	if (fc_xl_xinfo.xinfo & XACT_XINFO_HAS_DBINFO)
		XLogRegisterData((char *) (&fc_xl_dbinfo), sizeof(fc_xl_dbinfo));

	if (fc_xl_xinfo.xinfo & XACT_XINFO_HAS_SUBXACTS)
	{
		XLogRegisterData((char *) (&fc_xl_subxacts),
						 MinSizeOfXactSubxacts);
		XLogRegisterData((char *) fc_subxacts,
						 fc_nsubxacts * sizeof(TransactionId));
	}

	if (fc_xl_xinfo.xinfo & XACT_XINFO_HAS_RELFILENODES)
	{
		XLogRegisterData((char *) (&fc_xl_relfilenodes),
						 MinSizeOfXactRelfilenodes);
		XLogRegisterData((char *) fc_rels,
						 fc_nrels * sizeof(RelFileNode));
	}

	if (fc_xl_xinfo.xinfo & XACT_XINFO_HAS_DROPPED_STATS)
	{
		XLogRegisterData((char *) (&fc_xl_dropped_stats),
						 MinSizeOfXactStatsItems);
		XLogRegisterData((char *) fc_droppedstats,
						 fc_ndroppedstats * sizeof(xl_xact_stats_item));
	}

	if (fc_xl_xinfo.xinfo & XACT_XINFO_HAS_TWOPHASE)
	{
		XLogRegisterData((char *) (&fc_xl_twophase), sizeof(xl_xact_twophase));
		if (fc_xl_xinfo.xinfo & XACT_XINFO_HAS_GID)
			XLogRegisterData(unconstify(char *, fc_twophase_gid), strlen(fc_twophase_gid) + 1);
	}

	if (fc_xl_xinfo.xinfo & XACT_XINFO_HAS_ORIGIN)
		XLogRegisterData((char *) (&fc_xl_origin), sizeof(xl_xact_origin));

	if (TransactionIdIsValid(fc_twophase_xid))
		XLogSetRecordFlags(XLOG_INCLUDE_ORIGIN);

	return XLogInsert(RM_XACT_ID, fc_info);
}

/*
 * 在 9.0 之前这是一个相对较短的函数，但现在执行许多操作，其执行顺序
 * 至关重要。
 */
static void fc_xact_redo_commit(xl_xact_parsed_commit *fc_parsed,
				 TransactionId fc_xid,
				 XLogRecPtr fc_lsn,
				 RepOriginId fc_origin_id)
{
	TransactionId fc_max_xid;
	TimestampTz fc_commit_time;

	Assert(TransactionIdIsValid(fc_xid));

	fc_max_xid = TransactionIdLatest(fc_xid, fc_parsed->nsubxacts, fc_parsed->subxacts);

	/* 确保 nextXid 超过记录中提到的任何 XID。 */
	AdvanceNextFullTransactionIdPastXid(fc_max_xid);

	Assert(((fc_parsed->xinfo & XACT_XINFO_HAS_ORIGIN) == 0) ==
		   (fc_origin_id == InvalidRepOriginId));

	if (fc_parsed->xinfo & XACT_XINFO_HAS_ORIGIN)
		fc_commit_time = fc_parsed->origin_timestamp;
	else
		fc_commit_time = fc_parsed->xact_time;

	/* 设置事务提交时间戳和元数据 */
	TransactionTreeSetCommitTsData(fc_xid, fc_parsed->nsubxacts, fc_parsed->subxacts,
								   fc_commit_time, fc_origin_id);

	if (standbyState == STANDBY_DISABLED)
	{
		/*
		 * 在 pg_xact 中标记事务已提交。
		 */
		TransactionIdCommitTree(fc_xid, fc_parsed->nsubxacts, fc_parsed->subxacts);
	}
	else
	{
		/*
		 * 如果到达的事务完成记录包含尚未观察到的子事务，则
		 * 该记录尚未由 xlog.c 的主恢复循环中 RecordKnownAssignedTransactionIds()
		 * 调用完全处理。因此，我们需要再次进行会计处理以覆盖该情况。
		 * 这很令人困惑，并且很容易认为这个调用是无关的，这在开发中
		 * 已经发生过三次。保留它。
		 */
		RecordKnownAssignedTransactionIds(fc_max_xid);

		/*
		 * 在 pg_xact 中标记事务已提交。我们在恢复期间使用异步提交协议
		 * 来提供用户尝试设置提示位时有关数据库一致性的信息。
		 * 在 minRecoveryPoint 超过此提交记录之前，不设置提示位非常重要。
		 * 这确保如果我们崩溃，我们不会看到尚未恢复的事务所做的更改
		 * 被设置了提示位。虽然这不太可能，但安全起见很好。
		 */
		TransactionIdAsyncCommitTree(fc_xid, fc_parsed->nsubxacts, fc_parsed->subxacts, fc_lsn);

		/*
		 * 在更新 ProcArray 之前我们必须标记 clog。
		 */
		ExpireTreeKnownAssignedTransactionIds(fc_xid, fc_parsed->nsubxacts, fc_parsed->subxacts, fc_max_xid);

		/*
		 * 发送任何与提交相关的缓存无效化。我们必须保持与 CommitTransaction()
		 * 中发生的无效化相同的顺序，然后释放锁。
		 */
		ProcessCommittedInvalidationMessages(fc_parsed->msgs, fc_parsed->nmsgs,
											 XactCompletionRelcacheInitFileInval(fc_parsed->xinfo),
											 fc_parsed->dbId, fc_parsed->tsId);

		/*
		 * 释放锁（如果有）。我们对两阶段和普通的一阶段事务都这样做。
		 * 实际上我们忽略了准备阶段，直接进入锁释放。
		 */
		if (fc_parsed->xinfo & XACT_XINFO_HAS_AE_LOCKS)
			StandbyReleaseLockTree(fc_xid, fc_parsed->nsubxacts, fc_parsed->subxacts);
	}

	if (fc_parsed->xinfo & XACT_XINFO_HAS_ORIGIN)
	{
		/* 恢复应用进度 */
		replorigin_advance(fc_origin_id, fc_parsed->origin_lsn, fc_lsn,
						   false /* 向后 */ , false /* WAL */ );
	}

	/* 确保应该被丢弃的文件被丢弃 */
	if (fc_parsed->nrels > 0)
	{
		/*
		 * 首先更新最小恢复点以覆盖此WAL记录。一旦
		 * 关系被删除，就无法回头。缓冲管理器
		 * 强制普通更新关系文件时遵循WAL优先规则，
		 * 以确保最小恢复点始终在相应的数据文件更改
		 * 被刷新到磁盘之前更新，但在这里我们
		 * 必须执行相同的操作，因为我们绕过了缓冲管理器。
		 *
		 * 在删除文件之前这样做意味着，如果由于某种原因
		 * 删除失败，则即使重新启动后也无法启动系统，
		 * 直到修复底层情况以使删除成功。或者，我们可以在删除
		 * 后更新最小恢复点，但这将留下一个小窗口，在
		 * 该窗口中会违反WAL优先规则。
		 */
		XLogFlush(fc_lsn);

		/* 确保应该被丢弃的文件被丢弃 */
		DropRelationFiles(fc_parsed->xnodes, fc_parsed->nrels, true);
	}

	if (fc_parsed->nstats > 0)
	{
		/* 参见上面的关系等效调用 */
		XLogFlush(fc_lsn);

		pgstat_execute_transactional_drops(fc_parsed->nstats, fc_parsed->stats, true);
	}

	/*
	 * 我们调用XLogFlush()的原因与在正常操作中发出
	 * ForceSyncCommit()是相同的。例如，在创建数据库时，
	 * 我们从模板数据库复制所有文件，然后提交
	 * 事务。如果在所有文件复制完成但在提交之前
	 * 崩溃，则您在数据目录中会有没有在pg_database中
	 * 的条目的文件。为了最小化这一窗口，我们使用
	 * ForceSyncCommit()尽快将提交记录轰送到磁盘。
	 * 在恢复过程中我们有同样的窗口，强制执行XLogFlush()
	 * （在恢复期间更新minRecoveryPoint）有助于减小该问题窗口，
	 * 对于任何请求ForceSyncCommit()的用户。
	 */
	if (XactCompletionForceSyncCommit(fc_parsed->xinfo))
		XLogFlush(fc_lsn);

	/*
	 * 如果主节点要求（因为有人在等待同步
	 * 提交 = remote_apply），我们需要立即请求walreceiver
	 * 发送回复。
	 */
	if (XactCompletionApplyFeedback(fc_parsed->xinfo))
		XLogRequestWalReceiverReply();
}

/*
 * 注意执行顺序，与xact_redo_commit()相同。
 * 这两个函数类似，但在关键地方有所不同。
 *
 * 还要注意，回滚可以是对子事务及其子代的，
 * 不仅仅是顶层回滚。这意味着我们必须考虑
 * topxid != xid，而在提交时我们会发现
 * topxid == xid始终成立，因为子事务提交
 * 从不记录在WAL中。
 */
static void fc_xact_redo_abort(xl_xact_parsed_abort *fc_parsed, TransactionId fc_xid,
				XLogRecPtr fc_lsn, RepOriginId fc_origin_id)
{
	TransactionId fc_max_xid;

	Assert(TransactionIdIsValid(fc_xid));

	/* 确保 nextXid 超过记录中提到的任何 XID。 */
	fc_max_xid = TransactionIdLatest(fc_xid,
								  fc_parsed->nsubxacts,
								  fc_parsed->subxacts);
	AdvanceNextFullTransactionIdPastXid(fc_max_xid);

	if (standbyState == STANDBY_DISABLED)
	{
		/* 在pg_xact中标记事务为回滚，无需异步处理 */
		TransactionIdAbortTree(fc_xid, fc_parsed->nsubxacts, fc_parsed->subxacts);
	}
	else
	{
		/*
		 * 如果到达的事务完成记录包含尚未观察到的子事务，则
		 * 该记录尚未由 xlog.c 的主恢复循环中 RecordKnownAssignedTransactionIds()
		 * 调用完全处理。因此，我们需要再次进行会计处理以覆盖该情况。
		 * 这很令人困惑，并且很容易认为这个调用是无关的，这在开发中
		 * 已经发生过三次。保留它。
		 */
		RecordKnownAssignedTransactionIds(fc_max_xid);

		/* 在pg_xact中标记事务为回滚，无需异步处理 */
		TransactionIdAbortTree(fc_xid, fc_parsed->nsubxacts, fc_parsed->subxacts);

		/*
		 * 我们必须在标记 clog 后更新 ProcArray。
		 */
		ExpireTreeKnownAssignedTransactionIds(fc_xid, fc_parsed->nsubxacts, fc_parsed->subxacts, fc_max_xid);

		/*
		 * 没有要发送或撤销的失效消息。
		 */

		/*
		 * 释放锁，如果有的话。没有要发送的失效消息。
		 */
		if (fc_parsed->xinfo & XACT_XINFO_HAS_AE_LOCKS)
			StandbyReleaseLockTree(fc_xid, fc_parsed->nsubxacts, fc_parsed->subxacts);
	}

	if (fc_parsed->xinfo & XACT_XINFO_HAS_ORIGIN)
	{
		/* 恢复应用进度 */
		replorigin_advance(fc_origin_id, fc_parsed->origin_lsn, fc_lsn,
						   false /* 向后 */ , false /* WAL */ );
	}

	/* 确保应该被丢弃的文件被丢弃 */
	if (fc_parsed->nrels > 0)
	{
		/*
		 * 参见关于在截断时更新最小恢复点的注释，
		 * 在 xact_redo_commit() 中。
		 */
		XLogFlush(fc_lsn);

		DropRelationFiles(fc_parsed->xnodes, fc_parsed->nrels, true);
	}

	if (fc_parsed->nstats > 0)
	{
		/* 参见上面的关系等效调用 */
		XLogFlush(fc_lsn);

		pgstat_execute_transactional_drops(fc_parsed->nstats, fc_parsed->stats, true);
	}
}

void xact_redo(XLogReaderState *fc_record)
{
	uint8		fc_info = XLogRecGetInfo(fc_record) & XLOG_XACT_OPMASK;

	/* 在 xact 记录中不使用备份块 */
	Assert(!XLogRecHasAnyBlockRefs(fc_record));

	if (fc_info == XLOG_XACT_COMMIT)
	{
		xl_xact_commit *fc_xlrec = (xl_xact_commit *) XLogRecGetData(fc_record);
		xl_xact_parsed_commit fc_parsed;

		ParseCommitRecord(XLogRecGetInfo(fc_record), fc_xlrec, &fc_parsed);
		fc_xact_redo_commit(&fc_parsed, XLogRecGetXid(fc_record),
						 fc_record->EndRecPtr, XLogRecGetOrigin(fc_record));
	}
	else if (fc_info == XLOG_XACT_COMMIT_PREPARED)
	{
		xl_xact_commit *fc_xlrec = (xl_xact_commit *) XLogRecGetData(fc_record);
		xl_xact_parsed_commit fc_parsed;

		ParseCommitRecord(XLogRecGetInfo(fc_record), fc_xlrec, &fc_parsed);
		fc_xact_redo_commit(&fc_parsed, fc_parsed.twophase_xid,
						 fc_record->EndRecPtr, XLogRecGetOrigin(fc_record));

		/* 删除 TwoPhaseState gxact 条目和/或 2PC 文件。 */
		LWLockAcquire(TwoPhaseStateLock, LW_EXCLUSIVE);
		PrepareRedoRemove(fc_parsed.twophase_xid, false);
		LWLockRelease(TwoPhaseStateLock);
	}
	else if (fc_info == XLOG_XACT_ABORT)
	{
		xl_xact_abort *fc_xlrec = (xl_xact_abort *) XLogRecGetData(fc_record);
		xl_xact_parsed_abort fc_parsed;

		ParseAbortRecord(XLogRecGetInfo(fc_record), fc_xlrec, &fc_parsed);
		fc_xact_redo_abort(&fc_parsed, XLogRecGetXid(fc_record),
						fc_record->EndRecPtr, XLogRecGetOrigin(fc_record));
	}
	else if (fc_info == XLOG_XACT_ABORT_PREPARED)
	{
		xl_xact_abort *fc_xlrec = (xl_xact_abort *) XLogRecGetData(fc_record);
		xl_xact_parsed_abort fc_parsed;

		ParseAbortRecord(XLogRecGetInfo(fc_record), fc_xlrec, &fc_parsed);
		fc_xact_redo_abort(&fc_parsed, fc_parsed.twophase_xid,
						fc_record->EndRecPtr, XLogRecGetOrigin(fc_record));

		/* 删除 TwoPhaseState gxact 条目和/或 2PC 文件。 */
		LWLockAcquire(TwoPhaseStateLock, LW_EXCLUSIVE);
		PrepareRedoRemove(fc_parsed.twophase_xid, false);
		LWLockRelease(TwoPhaseStateLock);
	}
	else if (fc_info == XLOG_XACT_PREPARE)
	{
		/*
		 * 在 TwoPhaseState gxact 条目中存储 WAL 记录的 xid 和起始/结束指针。
		 */
		LWLockAcquire(TwoPhaseStateLock, LW_EXCLUSIVE);
		PrepareRedoAdd(XLogRecGetData(fc_record),
					   fc_record->ReadRecPtr,
					   fc_record->EndRecPtr,
					   XLogRecGetOrigin(fc_record));
		LWLockRelease(TwoPhaseStateLock);
	}
	else if (fc_info == XLOG_XACT_ASSIGNMENT)
	{
		xl_xact_assignment *fc_xlrec = (xl_xact_assignment *) XLogRecGetData(fc_record);

		if (standbyState >= STANDBY_INITIALIZED)
			ProcArrayApplyXidAssignment(fc_xlrec->xtop,
										fc_xlrec->nsubxacts, fc_xlrec->xsub);
	}
	else if (fc_info == XLOG_XACT_INVALIDATIONS)
	{
		/*
		 * XXX 我们现在忽略这一点，重要的是写入提交记录的失效
		 * 消息。
		 */
	}
	else
		elog(PANIC, "xact_redo: unknown op code %u", fc_info);
}
